I'll be as straightforward as possible:
I have a Challenge model, which holds information about a certain challenge and the amount of scores it gives. Any User can have multiple Submissions for a given challenge.
When querying for a specific Challenge, I want to return JSON with the Challenge's information along with the maximum score that the current request.user has scored. Is there a way to somehow ducktype (attach) the value to the model instance and serialize it with a given Serializer class?
What I've found is that ModelSerializer requires the model to have that field itself, where in my case I add it dynamically.
What's a good approach to this problem and has anybody had any similar problems?
this can be done by accessing the request.user in the serializer from extra context.
In generic views the model serializer is provided with extra context. This context has the request object.
you can define a method field on your Challenge serializer
class ChallengeSerializer(serializers.ModelSerializer):
user_max_score = serializers.SerializerMethodField()
class Meta:
model = Challenge
fields = '__all__'
def get_user_max_score(self, obj):
user_max_score = 0
request = self.context.get('request', None)
if request:
user = request.user
# get the user's max score
...
return user_max_score
You can provide extra context too if you are not using generic views. Read more here: http://www.django-rest-framework.org/api-guide/serializers/#including-extra-context
Related
Is there a way to limit what fields are populated (such as in dropdown selectors or list selectors) in the DRF browsable API?
Below is an image example of how DRF is suggesting choices of "projects" to the user that he should select. However, the logged in user may or may not have access to these projects, so I'd like to get control over what shows up here! It seems that the default behavior is to show all related objects.
It would be really useful if there was a way to link the objects populated in these fields to be set according to a get_queryset() function.
This page seems to hint that it might be possible, I just can't find an example of how to do it: http://www.django-rest-framework.org/api-guide/filtering/
You can define a new field based on some of the serializers.RelatedField-ish classes, and use the request in the context to redefine what the method get_queryset returns.
An example that might work for you:
class AuthorRelatedField(serializers.HyperlinkedRelatedField):
def get_queryset(self):
if 'request' not in self.context:
return Author.objects.none()
request = self.context['request']
return Author.objects.filter(user__pk=request.user.pk)
class BookSerializer(serializers.HyperlinkedModelSerializer):
author = AuthorRelatedField(view_name='author-detail')
Check the templates in DRF/temapltes/rest_framework/inline/select.html. The select object in DRF uses the iter_options method of the field to limit the options in the select tag, which in turn uses the queryset from get_queryset.
This would efectively limit the options of the select tag used in the browsable API. You could filter as well in the get_choices method if you want to preserve the queryset (check the code in DRF/relations.py). I am unsure if this impacts the Admin.
I don't fully understand what do you want but try queryset filter's __in function if you need to show only exact values:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
to_show = [ "user1", "user2", "user3"]
queryset = Purchase.objects.all()
username = self.request.query_params.get('username', None)
if username is not None:
queryset = queryset.filter(purchaser__username__in=username)
return queryset
you can add your values to to_show list and if queryset element equals one of them then it will be shown.
Also if you want to show only some fields of model you need to edit your Serializer's fields parameter:
class PurchaseList(serializers.ModelSerializer):
class Meta:
model = Purchase
fields = ('id', 'field1', 'field2', ...)
Using latest version of Django and DRF.
I have a rather complex requirement I can't find a solution for. I'll try to simplify it.
Let's say I have a model that has two fields. field_a and field_b
I have a ModelSerializer for it. I POST a request with its fields. The fields get validated with the model and then against my two serializer functions validate_field_a and validate_field_b. All is well.
Now I'd like my POST request to include a third field that is not a member of that model. let's call it field_c. I have a custom def create(self, validated_data): in my serializer which saves everything to the database.
with regards to field_c I would like to:
Custom Validate it. just like I do with the other two fields.
Require that it is mandatory for the whole request to succeed and if it's not, issue a "Field is required" error just like if I forgot to POST one of my required model fields.
Have the chance to take field_c and save it onto a totally different unrelated Model's row in the db.
I can't seem to get around that. If I add field_c to the fields meta - it throws an exception saying justifiably that field_c is not in my model. If I don't include it in fields, the validate_field_c which I really want to put there doesn't even get called.
What can I do?
You can add the custom field in your serializer as a write_only field and override the create method so that you can handle the custom field's value.
Something like this:
class MySerializer(serializers.ModelSerializer):
field_c = serializers.CharField(write_only=True)
class Meta:
model = MyModel
fields = ('field_a', 'field_b', 'field_c')
def validate_field_c(self, value):
if value is 'test':
raise ValidationError('Invalid')
return value
def create(self, validated_data, **kwargs):
field_c = validated_data.pop('field_c')
return MyModel.objects.create(**validated_data)
Don't use ModelSerializer for this - use a serializer that recreates the same fields as your model & include field_c as you would.
I understand that you want your model to do some of the work in the validation process but the design of DRF is such that it isolates these responsibilities. You can read more about it here. Basically, the serializer should be the one doing all the validation heavy-lifting.
Of course, this means that you'll have to explicitly define the validation methods in the serializer.
In your custom create() method you can create the model instance or do whatever you want in it as required.
I have a model named Domain which looks like this:
class Domain(models.Model):
"""
Model for storing the company domains
"""
user = models.ForeignKey(
User
)
host = models.CharField(
null=False, verbose_name="Host", max_length=128, unique=True
)
I'd like to use Django's generic views for doing CRUD operations on this. There is one field in this model that needs user input but the foreign key field doesn't need any user input. How can I exclude that field from the form that my generic view generates but assign it the value of the current authenticated user.
Thanks.
Have a look at Russel's answer to a similar question on the django-users group earlier this week.
Quoting the answer*:
Forms and Views solve different problems.
The View is solving the problem of "how do I handle this request and
convert it into a response?". The Form is solving the problem of "How
do I convert the POST data in this request into a model object (or a
change to a model object)?".
Very roughly, a view is doing the following:
View gets a request
View works out whether this is a GET or a POST
If its a POST, View asks the Form to turn the Post into a model change
Form returns success or failure
View responds to the success or failure of the Form.
View returns a response.
The functionality of the Form is a complete subset of the
functionality of the View -- and for this reason, it's a completely
interchangable internal component.
Now, in simple situations, it's possible for a View to guess all the
defaults for the form -- all it needs to know is that you're dealing
with a Foo model, and it can construct a default Foo ModelForm.
However, if you have more sophisticated form requirements, you're
going to need a customized Form.
We could have implemented this by exposing all the options of
ModelForm on the View class; but in order to keep everything clean, we
kept the ModelForm isolated, and provided the View with a way to
specify which Form class it's going to use.
So - to cover your use case of excluding fields, you define a
ModelForm that excludes the fields, then let the CreateView know the
form you want to use:
class CampaignForm(forms.ModelForm):
class Meta:
model = Campaign
exclude = ('user', 'name', 'content_inlined')
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
I'm guessing when you say "fix a values for a field", you mean setting
the values of user, name and content_inlined before you save the new
Campaign instance; to do this, you need to inject some extra code into
the form processing logic of the form:
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
def form_valid(self, form):
form.instance.user = ... (something meaningful.. e.g., self.request.user)
return super(CreateCampaignView, self).form_valid(form)
This overrides the default behavior when the form is valid, and sets
the extra values. The super() implementation of form_valid() will then
save the instance.
For the record, this could also be done by overriding the save()
method on the ModelForm -- however, if you do that, you lose the
request object, which you will need if you're trying to set the
instance values to something that is request-sensitive.
*the original answer set self.object.user instead of form.instance.user. This gives an AttributeError so I have changed it above.
I am having trouble using the SlugRelatedField to deserialize a field that is part of unique_together set.
To given an example, I have a Blog model which has a Title and an Author.
The tuple of these uniquely identify a Blog (as in the Title is unique to a given Author, but not unique site wide). I want to look up the Blog from a message containing just these values.
In order to deserialize a message (i.e. from_native), the SlugRelatedField calls: self.queryset.get(**{self.slug_field: data}) which will fail on the Title slug because it is not globally unique. This can be solved by providing a more limited queryset (ie one which contains just Blogs but that user), but I am not sure how/where is the best place to set this queryset in the Field (because I do not know the Author until I get to deserializing that field).
One idea would be to do my own deserialization of the Author in get_fields where I could then filter the queryset for Blog. This code is pretty ugly, likely results in deserialization of Author twice, and has issues when the Serializer is used for the list view (as opposed to the detail view).
You need to set the QuerySet for your field at runtime.
You can do this in the serialiser, something like:
class MyObjectSerializer(serializers.HyperlinkedModelSerializer):
def get_fields(self, *args, **kwargs):
fields = super(MyObjectSerializer, self).get_fields(*args, **kwargs)
fields['slug'].queryset = MyObject.objects.filter(self.context['view'].request.user)
return fields
Or in the view as:
def get_serializer(self):
serializer = super(MyObjectView, self).get_serializer()
serializer.fields['slug'].queryset = MyObject.objects.filter(self.request.user)
It's the same thing — you're setting the QuerySet on the slug field once you have a reference to the current user — it just depends on which is best for you.
I hope that helps.
I'm making a settings interface which works by scanning for a settings folder in the installed applications, scanning for settings files, and finally scanning for ModelForms.
I'm at the last step now. The forms are properly found and loaded, but I now need to provide the initial data. The initial data is to be pulled from the database, and, as you can imagine, it must be limited to the authenticated user (via request.user.id).
Keep in mind, this is all done dynamically. None of the names for anything, nor their structure is known in advanced (I really don't want to maintain a boring settings interface).
Here is an example settings form. I just pick the model and which fields the user can edit (this is the extent to which I want to maintain a settings interface).
class Set_Personal_Info(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('nick_name', 'url')
I've looked at modelformset_factory which almost does what I want to do, but it only seems to work with results of two or more. (Here, obj is one of the settings forms)
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.filter(id=request.user.id))
I can't filter the data, I have to get one, and only one result. Unfortunately I can't use get()
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.get(id=request.user.id))
'User' object has no attribute 'ordered'
Providing the query result as initial data also doesn't work as it's not a list.
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(initial=obj.Meta.model.objects.get(id=request.user.id))
'User' object does not support indexing
I have a feeling that the answer is right in front of me. How can I pull database from the database and shove it into the form as initial values?
I'm not really sure I understand what you're trying to do - if you're just interested in a single form, I don't know why you're getting involved in formsets at all.
To populate a modelform with initial data from the database, you just pass the instance argument:
my_form = Set_Personal_Info(instance=UserProfile.objects.get(id=request.user.id))
Don't forget to also pass the instance argument when you're instantiating the form on POST, so that Django updates the existing instance rather than creating a new one.
(Note you might want to think about giving better names to your objects. obj usually describes a model instance, rather than a form, for which form would be a better name. And form classes should follow PEP8, and probably include the word 'form' - so PersonalInfoForm would be a good name.)
Based on what I've understand ... if you want to generate a form with dynamic fields you can use this:
class MyModelForm(forms.ModelForm):
def __init__(self, dynamic_fields, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields = fields_for_model(self._meta.model, dynamic_fields, self._meta.exclude, self._meta.widgets)
class Meta:
model = MyModel
Where dynamic_fields is a tuple.
More on dynamic forms:
http://www.rossp.org/blog/2008/dec/15/modelforms/
http://jacobian.org/writing/dynamic-form-generation/
http://dougalmatthews.com/articles/2009/dec/16/nicer-dynamic-forms-django/
Also Daniel's approach is valid and clean ... Based on your different ids/types etc you can you use different Form objects
forms.py
class MyModelFormA(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_a','field_b','field_c')
class MyModelFormB(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_d','field_e','field_f')
views.py
if request.method == 'POST':
if id == 1:
form = MyModelFormA(data=request.POST)
elif id == 2:
form = MyModelFormB(data=request.POST)
else:
form = MyModelFormN(data=request.POST)
if form.is_valid():
form.save() else:
if id == 1:
form = MyModelFormA()
elif id == 2:
form = MyModelFormB()
else:
form = MyModelFormN()