Reputation:
Is there a way to create a field which is A text field, but, with choices. that the choices are the field existing objects? And if the word passed in is not in the existing data, add it?
I've looked across all the internet for a field like this or example of it, either in Django or the rest framework, but could not find one.
My use for it, for example, would be:
class Point(models.Model):
location_name = models.TextField(verbose_name="Location name",
unique=True,
# This would include the existing names.
choices=,
)
latitude = models.FloatField(name="GDT1Latitude",
verbose_name="GDT 1 Latitude",
unique=False, max_length=255, blank=False,
help_text="Enter the location's Latitude, first when extracting from Google Maps.",
default=DEFAULT_VALUE)
longitude = models.FloatField(name="GDT1Longitude",
verbose_name="GDT 1 Longitude",
unique=False, max_length=255, blank=False,
help_text="Enter the location's Longitude, second when extracting from Google Maps.",
default=DEFAULT_VALUE)
So, It could be used when making a post request instead of already typing the latitude, longitude It would be fetched from the exsiting object.
Upvotes: 1
Views: 644
Reputation:
Relying on the logic Timothe proposed here I have constructed a kind of workaround to the problem I presented.
First, the model using the Point model as choices field:
class Mission(models.Model):
id = models.UUIDField(primary_key=False, default=uuid.uuid4, editable=False)
mission_name = models.CharField(name='MissionName',
verbose_name="Mission Name",
max_length=255,
blank=False,
help_text="Enter the mission's name",
unique=True,
primary_key=True
# We want to be able to query missions not let them be confused with other missions.
)
uav_lat = models.FloatField(name="UavLatitude",
verbose_name="UAV Latitude",
unique=False, max_length=255, blank=False,
help_text="Enter the location's Latitude, first when extracting from Google Maps.",
default=DEFAULT_VALUE)
uav_lon = models.FloatField(name="UavLongitude",
verbose_name="UAV Longitude",
unique=False, max_length=255, blank=False,
help_text="Enter the location's Longitude, second when extracting from Google Maps.",
default=DEFAULT_VALUE)
gdt = models.CharField(verbose_name='Locations',
max_length=20)
the gdt field does not inherit directly from the point model, in order for me to do the following:
Here I can't show exactly what I did, but at first, I just added another field called add_gdt, which is a related field to the knownlocation model.
And as Timothe suggested, if it's none choose the gdt field, if no choice was made on gdt, than add add_gdt field to the knownlocation db as regular related field.
Serializer:
from Utils.APIConsts.Custom import get_locations, known_locations_data
from django.utils.functional import lazy
from rest_framework.fields import ChoiceField, DictField
from rest_framework.serializers import HyperlinkedModelSerializer
from .models import Mission
class MissionSerializer(HyperlinkedModelSerializer):
gdt = ChoiceField(choices=lazy(get_locations, tuple)())
locations = DictField(known_locations_data)
class Meta:
model = Mission
fields = ('Area', 'gdt', 'MissionName', 'UavLatitude', 'UavLongitude', 'UavElevation',
'locations')
use gdt here as choices field, using django's lazy to create names corrsepnding to the field of the point model, meaning, the choices field will be composed of the names of the known points in the db.
What is the get_locations being called?
Utils.APIConsts.Custom.py :
def get_locations():
"""
Returns the names of the locations in KnownLocation model.
"""
return [loc.Name for loc in KnownLocation.objects.all()]
def get_known_location():
queryset = KnownLocation.objects.all()
serialized_data = KnownLocationSerializer(queryset,
many=True)
locations_data = serialized_data.data
to_repr_dict = repack(data=locations_data, return_type='dict')
return to_repr_dict
known_locations_data = get_known_location()
Get the data being from the queryset, serialize it, get the data and created workable dictionary from it, with the following:
def repack(data, return_type=DICT):
"""
This function receives a list of ordered dictionaries to to unpack and reassign every dict to it's first value.
data: the data to unpack
return_type: specify the returned data type; dict or list.
'dict' is default
"""
try:
repacked_dict = {next(iter(d.values())): d for d in data}
if return_type is DICT:
return repacked_dict
elif return_type is LIST:
repacked_list = list(repacked_dict.items())
return repacked_list
raise TypeError('requested return type is not valid.')
except:
raise TypeError('data is not a list.')
def return_data_by_name(data, query) -> list:
"""
This function receive a nested dictionary and matches a given the query to the keys of the dictionary,
If there's a match, than that match is returned.
:param dict data:
:param str query:
"""
for name, values in data.items():
if query.lower() in name.lower():
values = list(values.values())
return values
Finally, The data being used in the view, using the return by name function to get the data.
gdt = return_data_by_name(data=get_known_location(), query=location_name)
Upvotes: 0
Reputation: 185
It sounds a little like you're looking for the django function get_or_create
. Given a model like Point
then you can call it like;
obj, created = Point.objects.get_or_create(
location_name ='Location',
defaults={'latitude': 1, 'longitude': 2},
)
and it returns an instance obj
and a boolean created
which indicates if this object is new or pre-existing.
Therefore if you give it an existing location_name
it will fetch the existing object for you, however if you give it a new one it will create a new object with that name.
Upvotes: 0
Reputation: 1470
At Database level, a field can not be both a "choices"-like and a free text field.
What you actually want is an abstraction on top of a couple of hidden DB fields
class Book(models.Model):
THEME_CHOICES = (
('Horror', 'Horror'),
('Romance', 'Romance')
)
_theme = models.CharField(choices=THEME_CHOICES, max_length=258, null=True, blank=True)
_other_theme = models.CharField(max_length=258)
@property
def theme(self):
if self._other_theme:
return self._other_theme
else:
return self._theme
This is just the first "DB-level", but you'll also need to custom '.save()' method, add a setter with @property.setter
.
Another option would be to write this abstraction at Form
level instead of Model
level.
Upvotes: 1