I want write a custom form field (and possibly widget too) and I'm not sure about how the form instances are shared between requests. For example, if I render a form with data from a model instance, is that instance still available when I am validating data? If so, does that mean that there is another database hit to look up the model again between requests?
Similarly, if I write a custom field that takes in a list of data to display in its __init__ method, will that list of data be available to validate against when the user POSTs the data?
It would be really helpful if someone could point me to parts of the django source where this occurs. I've been looking at the models.py, forms.py, fields.py and widgets.py from django.forms, but I'm still not 100% sure how it all works out.
Eventually, what I want to do is have a field that works something like this (the key part is the last line):
class CustomField(ChoiceField):
def __init__(self, data_dict, **kwargs):
super(CustomField, self).__init__(**kwargs)
self.data_dict = data_dict
self.choices = data_dict.keys()
def validate(self, value):
if value not in self.data_dict:
raise ValidationError("Invalid choice")
else:
return self.data_dict[value]
Will that data_dict be available on the next request? If I create a custom forms.Form and initialize it with the data_dict, will that be available on the next request? (e.g. with a factory method or something...).
Side note: I'm doing this because I want to (eventually) use something like Bootstrap's typeahead and I'd like to pass it "pretty values" which I then convert server-side (basically, like how option values in a select can have a different submitted value). I've done this with client-side javascript in the past, but it would be nice to consolidate it all into a form field.
There's nothing magical about forms. Like everything else in Django (or just about any web framework), objects don't persist between requests, and need to be reinstantiated each time. This happens in the normal view pattern for form handling: you instantiate it once for a POST, and a separate time for a GET. If you have data associated with the form, it would need to be passed in each time.
Related
I'm using Django Rest Framework to handle token authentication for a mobile application for a school. In particular, when a student logs in, the mobile application sends a token to my Django backend, which then combines data from its database and some external data from another source. I found it easiest to use a generic RetrieveAPIView to accomplish what I needed.
My code is working, and my main question is around the url. For most retrievals, we usually have the primary key as well (e.g. /students/SOME-ID), but in this case, I'm using the token to retrieve the user rather than the primary key. In fact, if SOME-ID passed in was different from the Token, the user associated with the Token would be returned anyway (which seems kinda strange).
I'm wondering whether it is better to have my url route be just (/students) instead though this seems to be a list rather than a retrieve operation.
WHAT I HAVE NOW
http://localhost:8000/api/v1/revision/students/1
IS THIS BETTER
http://localhost:8000/api/v1/revision/students/
CODE
class StudentView(generics.RetrieveAPIView):
model = Student
serializer_class = StudentSerializer
# combines data from both current and legacy database
def retrieve(self, request, pk=None):
obj = get_object_or_404(Student, user=request.user)
# KIV -> unsure if this is the best way to combine data from legacy and current database
# or should it be done in the serializer
data = StudentSerializer(obj).data
# combines existing data stored in database with legacy data from database
legacy_data = SOME_EXTERNAL_API_SERVICE.get_student_info(obj)
data['avatar'] = legacy_data['avatar']
data['coins'] = legacy_data['coins']
return Response(data)
I would definitely not use /students/id/ with the behaviour you're describing: This URL should always return the student with the given id of error (depending on whether the user fetching this resource is allowed to do so). You might want to use this URL for admins to view students in the future.
And for the same reason, I wouldn't use /students/ because I'd expect it to return a list of all students, or at least the list of all students the particular logged in user is allowed to see. This might fit your purpose now (where the logged in user can only see himself), but maybe not in the future if you create new roles that can view more students.
There are two approaches here:
Either you treat this as a filter on all the students: /students/?current=true which I personally find ugly because you're not actually filtering on the total set of students.
Or you treat this as a special case: /students/current using a special keyword for fetching this one specific student.
I would choose the latter one because it is more descriptive and easier to understand when looking at the API. Note of course that id can never be 'current' in this case, which is why some people discourage this kind of special resource queries and opt for the first option.
Definitely, the url http://localhost:8000/api/v1/revision/students/ looks better.
But you don't need to write this in a RetrieveAPIView, you could always do this in base APIView,
class StudentView(APIView):
def get(self, request, *args, **kwargs):
obj = get_object_or_404(Student, user=request.user)
data = StudentSerializer(obj).data
legacy_data = SOME_EXTERNAL_API_SERVICE.get_student_info(obj)
data['avatar'] = legacy_data['avatar']
data['coins'] = legacy_data['coins']
return Response(data)
By using like this, you can avoid the extra pk keyword argument from your url.
Using Django Rest Framework 3, Function Based Views, and the ModelSerializer (more specifically the HyperlinkedModelSerializer).
When a user submits a form from the client, I have a view that takes the request data, uses it to call to an external API, then uses the data from the external API to populate data for a model serializer.
I believe I have this part working properly, and from what I read, you are supposed to use context and validate()
In my model serializer, I have so far just this one overidden function:
from django.core.validators import URLValidator
def validate(self, data):
if 'foo_url' in self.context:
data['foo_url'] = self.context['foo_url']
URLValidator(data['foo_url'])
if 'bar_url' in self.context:
data['bar_url'] = self.context['bar_url']
URLValidator(data['bar_url'])
return super(SomeSerializer, self).validate(data)
Just in case, the relevant view code is like so:
context = {'request': request}
...
context['foo_url'] = foo_url
context['bar_url'] = bar_url
s = SomeSerializer(data=request.data, context=context)
if s.is_valid():
s.save(user=request.user)
return Response(s.data, status=status.HTTP_201_CREATED)
Now assuming I have the right idea going (my model does populate its foo_url and bar_url fields from the corresponding context data), where I get confused is how the validation is not working. If I give it bad data, the model serializer does not reject it.
I assumed that in validate(), by adding the context data to the data, the data would be checked for validity when is_valid() was called. Maybe not the case, especially when I print out s (after using the serializer but before calling is_valid()) there is no indication that the request object's data has been populated with the context data from validate() (I don't know if it should be).
So I tried calling the URLValidators directly in the validate() method, but still doesn't seem to be working. No errors despite giving it invalid data like 'asdf' or an empty python dict ({}). My test assertions show that the field indeed contains invalid data like '{}'.
What would be the proper way to do this?
You're not calling the validator.
By doing URLValidator(data['bar_url']) you're actually building an url validator with custom schemes (see the docs) and that's it. The proper code should be:
URLValidator()(data['bar_url'])
Where you build a default url validator and then validate the value.
But anyway I would not use this approach, what I would do instead is directly add the extra data (not using the context) and let DRF do the validation by declaring the right fields:
# Somewhere in your view
request.data['bar_url'] = 'some_url'
# In serializer:
class MySerializer(serializers.ModelSerializer):
bar_url = serializers.URLField()
class Meta:
fields = ('bar_url', ...)
To answer your comment
I also don't understand how this also manages to make it past the
Django's model validation
See this answer:
Why doesn't django's model.save() call full_clean()?
By default Django does not automatically call the .full_clean method so you can save a model instance with invalid values (unless the constraints are on the database level).
Complete DRF beginner here... I'm confused about the following concepts:
Let's say I POST some data, including a complex JSON blob for one of the fields, in order to create an object. Where should I actually create this object? Looking at the 3.1 docs, it seems like two places are equally valid for this: Serializer.create() and ViewSet.create(). How do I decide where to create my object and which way is considered "canonical"?
I understand that I need to run Serializer.is_valid() in order to validate the POSTed data. However, what is the difference between .data and .validated_data? They appear to be the same.
Finally, what is the "canonical" way to use a JSONField (e.g. django-jsonfield, but I'm not married to this package/implementation)? I have a model with several JSONFields and would like to use it "correctly" in DRF. I am aware of https://stackoverflow.com/a/28200902/585783, but it doesn't seem enough.
EDIT: My use case is an API POST that includes a complex JSON blob in one of the fields. I need to parse the JSON field, validate it, get/create several objects based on it, link new and existing objects, and finally store the JSON field in one of the new objects. So, I need to do custom validation for this JSON field by parsing it to python:
from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser
class MySerializer(serializers.ModelSerializer):
my_json_field = JSONSerializerField()
def validate_my_json_field(self, value):
stream = BytesIO(value)
list_of_dicts = JSONParser().parse(stream)
# do lots of validation to list_of_dicts
# render list_of_dicts back to a JSON string
return validated_list_of_dicts_as_json
Now, depending on which way I choose in Concept 1, I have to parse the validated JSON again to create my objects in create(), which doesn't feel right.
Thanks in advance!
The contents of HTTP requests (POST, GET, PUT, DELETE) will always be processed by the views (View, APIView, generic views, viewsets). The serializers are just part of how these views process the requests. Serializers are the "means" to connect the View layer with the Model layer. For what serializers do specifically, please read the first paragraph of the this page of the official docs.
To answer #1: you almost always do not need to touch either unless you have a very specific use case. In those extraordinary cases:
You override Serializer.create() if you have to customize how model
instances are converted into native Python objects and vice versa. (e.g. create multiple objects)
You override ViewSet.create() if you need to customize how the actual request itself will be processed. (e.g. if there is an additional query parameter in the request, add some response headers)
To answer #2, you almost never need to use is_valid() when using generic views or ViewSets. They already do it under the hood for you. The serializer's .data and .validated_data are a bit tricky to explain. The former contains the Python datatype representation of the queryset/model instances you want to serialize, while the latter is the result of the validation process involved in checking if a Python object conforms to that particular Python datatype representation mentioned earlier, which in turn can be converted into a model instance. If that did not make sense, refer to Serializing objects and Deserializing objects.
As for #3, what do you mean by JSON field? As far as I know, Django does not have a model field called JSONField. Is this from a third party package or your own custom written model field? If so, then you will probably have to find or write a package that will let you integrate it with DRF smoothly and "correctly" whatever that means.
EDIT
Your use case is too complicated. I can only give you rough code for this one.
class MyModelSerializer(serializers.ModelSerializer):
my_json_field = JSONSerializerField()
class Meta:
model = MyModel
def validate(self, data):
# Get JSON blob
json_blob = data['my_json_field']
# Implement your own JSON blob cleanup method
# Return None if invalid
json_blob = clean_json_blob(json_blob)
# Raise HTTP 400 with your custom error message if not valid
if not json_blob:
raise serializers.ValidationError({'error': 'Your error message'})
# Reassign if you made changes and return
data['my_json_field'] = json_blob
return data
def create(self, validated_data):
json_blob = validated_data['my_json_field']
# Implement your object creation here
create_all_other_objects_from_json(json_blob)
# ...
# Then return a MyModel instance
return my_model
Here is what I've been struggling for a day...
I have a Message model in which recipients is a ManyToManyField to the User model.
Then there is a form for composing messages. As there are thousands of users, it is not convenient to display the options in a multiple select widget in the form, which is the default behavior. Instead, using FcbkComplete jquery plugin, I made the recipients field look like an input field where the user types the recipients, and it WORKS.
But...
Although not visible on the form page, all the user list is rendered into the page in the select field, which is something I don't want for obvious reasons.
I tried overriding the ModelChoiceField's behavior manipulating validation and queryset, I played with the MultipleChoice widget, etc. But none of them worked and felt natural.
So, what is the (best) way to avoid having the whole list of options on the client side, but still be able to validate against a queryset?
Have you seen django-ajax-selects? I've never used it, but it's in my mental grab bag for when I come across a problem like what it sounds like you're trying to solve...
I would be trying one of two ways (both of which might be bad! I'm really just thinking out aloud here):
Setting the field's queryset to be empty (queryset = Model.objects.none()) and having the jquery tool use ajax views for selecting/searching users. Use a clean_field function to manually validate the users are valid.
This would be my preferred choice: edit the template to not loop through the field's queryset - so the html would have 0 options inside the select tags. That is, not using form.as_p() method or anything.
One thing I'm not sure about is whether #2 would still hit the database, pulling out the 5k+ objects, just not displaying them in the html. I don't think it should, but... not sure, at all!
If you don't care about suggestions, and is OK to use the ID, Django Admin comes with a raw_id_field attribute for these situations.
You could also make a widget, that uses the username instead of the ID and returns a valid user. Something among the lines of:
# I haven't tested this code. It's just for illustration purposes
class RawUsernameField(forms.CharField):
def clean(self, value):
try:
return User.objects.get(username=value)
except User.DoesNotExist:
rause forms.ValidationError(u'Invalid Username')
I solve this by overriding the forms.ModelMultipleChoiceField's default widget. The new widget returns only the selected fields, not the entire list of options:
class SelectMultipleUserWidget(forms.SelectMultiple):
def render_options(self, choices, selected_choices):
choices = [c for c in self.choices if str(c[0]) in selected_choices]
self.choices = choices
return super(SelectMultipleUserWidget,
self).render_options([], selected_choices)
class ComposeForm(forms.Form):
recipients = forms.ModelMultipleChoiceField(queryset=User.objects.all(),
widget=SelectMultipleUserWidget)
...
Here, I am a bit confused with forms in Django. I have information for the form(a poll i.e the poll question and options) coming from some db_table - table1 or say class1 in models. Now the vote from this poll is to be captured which is another model say class2. So, I am just getting confused with the whole flow of forms, here i think. How will the data be captured into the class2 table?
I was trying something like this.
def blah1()
get_data_from_db_table_1()
x = blah2Form()
render_to_response(blah.html,{...})
Forms have nothing to do with models in Django. They are just class meant to get informations from a dictionary (often request.POST) and check if each data linked to a key match a type and a format (e.g: is this a string of the form "bla#foo.tld").
You can ask django to create a form from a model, and in that case it will do its checking job, then if the data match, it will create a model, fill it and save it.
If a form is not created from a model, it will do nothing but checking. It will save nothing.
If it is created from a model, it will create a new instance of this particular model instance and save it.
If you want something more complicated, like, pre fill a form from various models or according to some conditions, or, say, you need to save several models according to the result of one form, you must do it manually.