Django Rest Framework serializers.DictField constraint on possible keys - django

I have a REST API output that looks like this:
{
"something": "xyz"
"something_dict" {
"a": 10,
"b": 20,
"c": 30
}
}
I'm trying to create a serializer for that (mostly for automatic documentation generation purposes, but I believe that doesn't really matter), and for the something_dict field I thought it could be a good idea to use serializers.DictField.
The problem is, the keys that can show up in the dictionary are limited to a certain set (values of an enum type), but I can't seem to find a good way to define it.
Something like serializers.DictField(key=serializers.ChoiceField(choices=[enum.value for enum in Enum])) would be great but that's obviously not available.
The only reasonable way to specify this is to use a nested serializer:
class SomethingDict(serializers.Serializer):
a = serializers.IntField(required=False)
b = serializers.IntField(required=False)
c = serializers.IntField(required=False)
But that's slightly inconvenient as the set of available keys is potentially dynamic (values of an enum, as mentioned previously).
The question is then:
Is it possible to specify possible values for serializer.DictField keys or is there any way around it to make it dynamic? (I'd like to avoid defining the class in dynamic way, if possible)

Django Rest Framework provides a DictField serializer field that can be used to handle dictionaries in your API.
You can add a constraint on the possible keys of the dictionary by subclassing DictField and overriding the to_internal_value() method to perform the necessary validation.
Have a look at this example,
from rest_framework import serializers
class ConstrainedDictField(serializers.DictField):
def to_internal_value(self, data):
# Perform validation on the keys of the dictionary here
if set(data.keys()) != {"key1", "key2"}:
raise serializers.ValidationError("Invalid keys for dictionary.")
return super().to_internal_value(data)
You can then use the ConstrainedDictField in your serializer like this:
class MySerializer(serializers.Serializer):
my_dict = ConstrainedDictField()
It will only accept dictionary with key key1 and key2.

Related

Flask-WTF default for a SelectField doesn't work for SQLAlchemy enum types

I have a Flask-SQLAlchemy site which uses the SQLAlchemy enum type for certain columns, to restrict possible input values.
For example, I have a "payment type" enum that has a few payment type options, like so:
class PaymentType(enum.Enum):
fixed = "fixed"
variable = "fixed_delivery"
quantity_discount = "quantity_discount"
When I use this in a dropdown/select I can specify the options like so:
prd_payment_type = SelectField(_('prd_payment_type'), choices=SelectOptions.PaymentTypes.All(0, _('PleaseSelect')))
The All() function I'm calling returns the different enum values as options, and also adds a custom "Please select..." option to the dropdown list. It looks like this:
class PaymentTypes(object):
#staticmethod
def All(blank_value=None, blank_text=None):
ret = [(i.value, _(i.name)) for i in PaymentType]
SelectOption.add_blank_item(ret, blank_value, blank_text)
return ret
So far, so good, I get a nice dropdown, with the correct options. the problem arises when I want to display the form using an already existing SQLAlchemy object from the database.
When I fetch the object from SQLAlchemy, the prd_payment_type property contains the enum PaymentType.quantity_discount, not just a string value 'quantity_discount':
Because WTForms (presumably) matches the pre-selected option in the dropdown to a string value "quantity_discount", it doesn't match the enum in the SQLAlchemy model and the option is not selected in the dropdown.
The SelectField of WTForms accepts a list of tuples containing strings as choices. I can't feed it my enum.
On the other hand, the SQLAlchemy model returns an enum type as the property for prd_payment_type. I could maybe override this property or something, but that feels like a lot of work to support SQLAlchemy's enums in WTForms.
Is there a standard solution for working with SQLAlchemy's enums in a WTForms SelectField or should I abandon using enums altogether and just store strings, maybe from a list of constants to keep them in check?
I have found a solution myself. It seems that when the enum has a
def __str__(self):
return str(self.value)
That's enough for WTForms to match the database value to the SelectField value.
I have made a baseclass that derives from enum.Enum and added the code above to return the enum value as a string representation.
This solution was based on these similar problems I found later on:
https://github.com/flask-admin/flask-admin/issues/1315
Python Flask WTForm SelectField with Enum values 'Not a valid choice' upon validation

How to access an integer key inside a django template if the dict has a same string key?

Suppose I've a dict like:
dic = {'1': 'string', 1 :'integer'}
When I pass it to a django template and try to access dic.1 then it always returns 'string'.
If I remove the key '1', then dic.1 returns 'integer'.
I know I can use a custom tag for this, something like:
from django import template
register = template.Library()
#register.filter
def get_key(value, arg):
return value.get(arg, None)
Then {{ dic|get_key:1 }} works fine.
But, is there a way to directly access the integer/float keys without using a custom tag?
Refer to the answer provided for the question on the topic of Dictionary access speed comparison with integer key against string key. Python (CPython) will always first try to find the string-based key before attempting to find any alternative.
You might have a very good reason to mix similar string keys with integer keys, but I would recommend avoiding this technique or type-casting the integers to strings to eliminate the inability to directly reference the key values.

Django: How to use django.forms.ModelChoiceField with a Raw SQL query?

I'm trying to render a form with a combo that shows related entities. Therefore I'm using a ModelChoiceField.
This approach works well, until I needed to limit which entities to show. If I use a simple query expression it also works well, but things break if I use a raw SQL query.
So my code that works, sets the queryset to a filter expression.
class ReservationForm(forms.Form):
location_time_slot = ModelChoiceField(queryset=LocationTimeSlot.objects.all(), empty_label="Select your prefered time")
def __init__(self,*args,**kwargs):
city_id = kwargs.pop("city_id") # client is the parameter passed from views.py
super(ReservationForm, self).__init__(*args,**kwargs)
# TODO: move this to a manager
self.fields['location_time_slot'].queryset = LocationTimeSlot.objects.filter(city__id = city_id )
BUT, if I change that to a raw query I start having problems. Code that does not work:
class ReservationForm(forms.Form):
location_time_slot = ModelChoiceField(queryset=LocationTimeSlot.objects.all(), empty_label="Select your prefered time")
def __init__(self,*args,**kwargs):
city_id = kwargs.pop("city_id") # client is the parameter passed from views.py
super(ReservationForm, self).__init__(*args,**kwargs)
# TODO: move this to a manager
query = """SELECT ts.id, ts.datetime_to, ts.datetime_from, ts.available_reserves, l.name, l.'order'
FROM reservations_locationtimeslot AS ts
INNER JOIN reservations_location AS l ON l.id = ts.location_id
WHERE l.city_id = %s
AND ts.available_reserves > 0
AND ts.datetime_from > datetime() """
time_slots = LocationTimeSlot.objects.raw(query, [city_id])
self.fields['location_time_slot'].queryset = time_slots
The first error I get when trying to render the widget is: 'RawQuerySet' object has no attribute 'all'
I could solve that one thanks to one of the commets in enter link description here, by doing:
time_slots.all = time_slots.__iter__ # Dummy fix to allow default form rendering with raw SQL
But now I'm getting something similar when posting the form:
'RawQuerySet' object has no attribute 'get'
Is there a proper way to prepare a RawQuerySet to be used by ModelChoiceField?
Thanks!
Are you sure you actually need a raw query there? Just looking at that query, I can't see any reason you can't just do it with filter(location__city=city_id, available_reserves__gte=0, datetime_from__gt=datetime.datetime.now()).
Raw query sets are missing a number of methods that are defined on conventional query sets, so just dropping them in place isn't likely to work without writing your own definitions for all those methods.
I temporarily fixed the problem adding the missing methods.
The way I'm currently using the ModelChoiceField I only needed to add the all() and get() methods, but in different scenarios you might need to add some other methods as well. Also this is not a perfect solution because:
1) Defining the get method this way migth produce incorrect results. I think the get() method is used to validate that the selected option is within the options returned by all(). The way I temporarily implemented it only validates that the id exists in the table.
2) I guess the get method is less performant specified this way.
If anyone can think of a better solution, please let me know.
So my temporary solution:
class LocationTimeSlotManager(models.Manager):
def availableSlots(self, city_id):
query = """SELECT ts.id, ts.datetime_to, ts.datetime_from, ts.available_reserves, l.name, l.'order'
FROM reservations_locationtimeslot AS ts
.....
.....
MORE SQL """
time_slots = LocationTimeSlot.objects.raw(query, [city_id])
# Dummy fix to allow default form rendering with raw SQL
time_slots.all = time_slots.__iter__
time_slots.get = LocationTimeSlot.objects.get
return time_slots

DRY in django queries, reverse queries, and predicates

I am frustrated that in Django I often end up having to write methods on a custom Manager:
class EntryManager(Manager):
def filter_beatle(self, beatle):
return self.filter(headline__contains=beatle)
... and repeat pretty much the same method in a different Manager for a reverse query:
class BlogManager(Manager):
def filter_beatle(self, beatle):
return self.filter(entry__headline__contains=beatle)
... and a predicate on Entry:
def headline_contains(self, beatle):
return self.headline.find(beatle) != -1
[Note that the predicate on Entry will work on Entry objects that haven't even been saved yet.]
This feels like a violation of DRY. Is there some way to express this once and use it in all three places?
What I would like to be able to do is write something like:
q = Q(headline__contains="Lennon")
lennon_entries = Entry.objects.filter(q)
lennon_blogs = Blog.objects.filter(q.reverse(Entry))
is_lennon = entry.would_filter(q)
... where 'headline__contains="Lennon"' expresses exactly once what it means to be 'an Entry about "Lennon"', and this can be used to construct reverse queries and a predicate.
The best place for this is a custom manager. According to django's guidelines a manager class is the best place for code that is affecting more than one object of a class.
class EntryManager(models.Manager):
def filter_lennons(self):
return self.get_query_set().filter(headline__contains='Lennon')
class Entry(models.Model):
headline = models.CharField(max_length=100)
objects = EntryManager()
lennons = Entry.objects.filter_lennons()
You should never rarely have to do the following:
if entry.headline.find('Lennon') >= 0:
because the filter should take care of restricting the result set to the instances you're interested in.
If you're going to be using the same filter multiple times, you can create a custom manager or a simple class method.
class Entry(models.Model):
...
# this really should be on a custom manager, but this was quicker to demonstrate
#classmethod
def find_headlines(cls, text):
return cls.objects.filter(headline__contains=text)
entries = Entry.find_headlines('Lennon')
But really, the DRYness has already been contained within the Queryset API. How often are you really going to be hard coding the string 'Lennon' into a query? Usually, the search parameter will be passed into a view from a GET or POST. Perfectly DRY.
So, what is the actual problem? Other than exploring the queryset API, have you ever had to hard code lookup values in multiple queries like your question?
For the "reverse filter" case you can use a subquery:
Blog.objects.filter(entries__in=Entry.objects.filter_beatle("Lennon"))
Reusing or generating predicates is not possible (in general) as there are predicates that cannot be expressed as queries and queries that cannot be expressed as predicates without db access.
My most common use for the predicate seems to be in asserts. Often something like:
class Thing(Model):
class QuerySet(query.QuerySet):
def need_to_be_whacked():
# ... code ...
def needs_to_be_whacked(self):
return Thing.objects.need_to_be_whacked().filter(id=self.id).exists()
def whack(self):
assert self.needs_to_be_whacked()
for thing in Thing.objects.need_to_be_whacked():
thing.whack()
I want to make sure that no other code is calling whack() in state where it doesn't need to be whacked. It costs a database hit, but it works.

How to serialize to json format a queryset that use the 'extra' statement in Django?

I want to serialize a QuerySet that contains an extra statement:
region_list = Region.objects.extra(select={ 'selected': 'case when id = %s then 1 else 0 end' % (new_region.id)}).all()
I use the statement below to serialize
return HttpResponse(serializers.serialize('json', region_list), mimetype='application/json')
But when I obtain the json results in the browser, only the fields of the Region model appears, the selected field dissapear.
How can I fix that?
One slightly longwinded solution would be to to dump the objects to JSON via django-piston's JSONEmitter class. When you register your Region model with piston, you can say what fields to include, and mention 'selected' there, and then use your annotation to make sure that the queryset used in the piston handler contains all the info you want.
Or just look at how piston does it and, if you don't want all of piston, just mimic the bits you do.