user12177026
user12177026

Reputation:

Django rest framework, field that is a choice or text

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

Answers (3)

user12177026
user12177026

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:

  • Point has been renamed to KnownLocation.

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

Maddo
Maddo

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

Timothé Delion
Timothé Delion

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

Related Questions