ModelForm fields order inaffected? [duplicate] - python

This question already has answers here:
How can I order fields in Django ModelForm?
(2 answers)
Closed 9 years ago.
trying to alter order of fields in admin ModelForm.
Bellow is my attempt, however order is kept unchanged. Added fields oi_number and vat_number are rendered at the end besides they are not at the end in self.fields SortedDict dictionary.
class ContactAdminForm(forms.ModelForm):
oi_number = fields_for_model(OrganizationExtra)['oi_number']
vat_number = fields_for_model(OrganizationExtra)['vat_number']
  # fields = ('organization', 'oi_number', 'vat_number')
# ^^^ this won't affect fields order either
class Meta:
model = Organization
def __init__(self, *args, **kwargs):
super(ContactAdminForm, self).__init__(*args, **kwargs)
try:
org_ix = self.fields.keyOrder.index('organization')
self.fields.keyOrder.insert(org_ix+1, self.fields.keyOrder[-2])
self.fields.keyOrder.insert(org_ix+2, self.fields.keyOrder[-1])
del self.fields.keyOrder[-2:]
except ValueError:
pass
Does get the order of fields resolved before __init__ method is called ? How can I change their order ?
Update:
The above ModelForm is used as a form in admin model which defines its own fields, so if I put all fields definition in above form, I'll get FieldError exception about unknown field name:
class ContactAdminForm(forms.ModelForm):
...
class Meta:
model = Organization
fields = ('organization', 'oi_number', 'vat_number')
class ContactOptionsEx(ContactOptions):
form = ContactAdminForm
admin.site.register(Contact, ContactOptionsEx)
# at attempt to render the form:
# FieldError at /admin/contact/contact/3/
# Unknown field(s) (organization) specified for Organization
However the field named organization does exist and is available in ContactAdminForm.__init__ method.

The error
Unknown field(s) (organization) specified for Organization
does not refer to a field on your form, but to a field on the model (Organization).
I think the problem here is that you are trying to add fields from a different Model (OrganizationExtra) to the ModelForm for Organization. There is always a one-to-one relation between a ModelForm and a Model. If you want to edit a related instance in the admin, you can use inlines:
class OrganizationExtraInline(admin.StackedInline):
model = OrganizationExtra
class ContactOptionsEx(ContactOptions):
inlines = ContactOptions.inlines + [OrganizationExtraInline]
# ...
If you want to limit the inline to one instance, use a OneToOneField or max_num = 1

Related

How to auto populate a read-only serializer field in django rest framework?

I have a question regarding django rest framework.
Most of the time, I have a serializer which has some read-only fields. For example, consider this simple model below:
class PersonalMessage(models.Model):
sender = models.ForeignKey(User, related_name="sent_messages", ...)
recipient = models.ForeignKey(User, related_name="recieved_messages", ...)
text = models.CharField(...)
def __str__(self) -> str:
return f"{self.text} (sender={self.sender})"
In this model, the value of sender and recipient should be automatically provided by the application itself and the user shouldn't be able to edit those fields. Alright, now take a look at this serializer:
class PersonalMessageSerializer(serializers.ModelSerializer):
class Meta:
model = PersonalMessage
fields = '__all__'
read_only_fields = ('sender', 'recipient')
It perfectly prevents users from setting an arbitrary value on the sender and recipient fields. But the problem is, when these fields are marked as read-only in the serializer, the serializer will completely ignore all the values that are passed into the constructor for these fields. So when I try to create a model, no values would be set for these fields:
PersonalMessageSerializer(data={**request.data, 'sender': ..., 'recipient': ...) # Won't work
What's the best way to prevent users from setting an arbitrary value and at the same time auto-populate those restricted fields in django rest framework?
Depending on how you get those two objects, you can use the serializer's save method to pass them, and they will automatically be applied to the object you are saving:
sender = User.objects.first()
recipient = User.objects.last()
serializer = PersonalMessageSerializer(data=request.data)
message = serializer.save(sender=sender, recipient=recipient)
The kwargs should match the field names in your model for this to work. For reference, have a look here
You able to override the serializer context like this;
PersonalMessageSerializer(data={**request.data, context={'sender': sender, 'recipent': recipent})
and catch the context inside serializer.
class PersonalMessageSerializer(serializers.ModelSerializer):
class Meta:
model = PersonalMessage
fields = '__all__'
read_only_fields = ('sender', 'recipient')
def validate(self, attrs):
attrs = super().validate(attrs)
attrs['sender'] = self.context['sender']
attrs['recipent'] = self.context['recipent']
return attrs
now serializer.validated_data it must returns sender and recipent.
From the question it is not possible to understand what field(s) of the relationship with sender and recipient you want to interact with, but a general answer can be found in the Serializer relations section of Django REST documentation.
Long story short, if you want to interact with one field only, you can use SlugRelatedField, which lets you interact with the target of the relationship using only one of its fields.
If it just the id, you can use PrimaryKeyRelatedField.
If you want to interact with more than one field, the way to go is Nested Relationships. Here you can specify a custom serializer for the target relationship, but you will have to override the create() method in your PersonalMessageSerializer to create the object from your relationship, as nested serializers are read-only by default.
So this is how you can make set a default on create but read only after in DRF. Although in this solution it wont actually be readonly, it's writable, but you now have explicit control on what the logged in user can write, which is the ultimate goal
Given the model
class PersonalMessage(models.Model):
sender = models.ForeignKey(User,...)
recipient = models.ForeignKey(User,..)
text = models.CharField(...)
You would first create your own custom default (I will show an example for only one field)
# Note DRF already has a CurrentUserDefault you can also use
class CurrentSenderDefault:
requires_context = True
def __call__(self, serializer_field):
return serializer_field.context['request'].user
def __repr__(self):
return '%s()' % self.__class__.__name__
Next you make your own field, that knows whats up with the filter.
This queryset prevents people from setting a value they are not allowed to. which is exactly what you want
class SenderField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
user = self.context['request'].user
if user:
queryset = User.objects.filter(id=user.id)
else:
queryset = User.objects.none()
return queryset
Finally on the serialiser you go
class PersonalMessageSerializer(serializers.ModelSerializer):
sender = SenderField(default=CurrentSenderDefault())
recipient = ...
class Meta:
model = PersonalMessage
fields = '__all__'
read_only_fields = ('sender', 'recipient')

Django: different behaviour in CreateView and UpdateView with unique constraint

class Badge(Model):
# ....
class Meta:
unique_together = ('identifier', 'restaurant')
Using a CreateView, when creating a Badge object whose identifier already exists, I actually get a form error, which is the expected behaviour.
But, using an UpdateView, when editing a Badge object whose identifier already exists, I don't get any form error, but a 500 error with duplicate key value violates unique constraint.
I can't understand why the behaviour differs. I'd like the form error to be shown in both cases.
I just realised for validation to work, that all fields need to be specified in the class based view, even if these fields should not be filled by the User.
class BadgesUpdateView(UpdateView):
model = Badge
# restaurant field must be included for validation even if the user does NOT fill it.
fields = ('identifier', 'is_active', 'owner', 'restaurant')
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['restaurant'].widget = forms.HiddenInput()
return form

How can I select specific fields in django rest framework? [duplicate]

This question already has answers here:
Django Rest Framework: Dynamically return subset of fields
(10 answers)
Closed 3 years ago.
For example, I have a Person model and its serializer
class Person(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
sex = models.IntegerField()
phone = models.CharField(max_length=255)
class SimplePersonSerializer(serializer.ModelSerializer):
class Meta:
model = Person
fields = ('first_name', 'last_name')
Then in my view function, I can:
#api_view(['GET'])
def people(request):
people = Person.objects.all()
data = SimplePersonSerializer(people, many=True).data
return Response(data)
However, when I profiler it using django-debug-toolbar, it shows that the serializer ask SQL Server to select all field of Person model, despite I only need first_name and last_name.
I know I can change people = Person.objects.all() to people = Person.objects.all().only('first_name', 'last_name') to make it. But I wonder if I can do this inside the serializer.
You can create dynamic field serializer for this and get the field data dynamically.
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class SimplePersonSerializer(DynamicFieldsModelSerializer):
class Meta:
model = Person
fields = '__all__'
And then you can use it in your views like this.
#api_view(['GET'])
def people(request):
fields = ('first_name', 'last_name')
people = Person.objects.all().only(fields)
data = SimplePersonSerializer(people, many=True, fields = fields).data
return Response(data)
This helps to improve performance because it will fetch only the required data. (when using Person.objects.all().only('first_name', 'last_name') to fetch specific data)
You get all the fields queried because that's the query that runs by default when you do .all etcetera. You only limit the fields (SELECT field1, field2, ...) when you do .only, .values, or .values_list.
You can you can define the fields inside the serializer or you can go further and do dynamic things like: https://github.com/wimglenn/djangorestframework-queryfields
Inside the serializer:
class Meta:
fields = (*,...)
But, this is specific to the serializer. As the name implies this is just serializing the returned data into objects.
You can do queries in the serializer, but this typically for custom fields.
No you cannot achieve that by using builtin features of django and rest_framework.
Since serializer tries to access fields for model, you can describe properties by setting #property in your model or define custom SerializerMethodField, all this could possibly use all fields of your model.
I add a class method setup_eager_loading for SimplePersonSerializer
class SimplePersonSerializer(serializer.ModelSerializer):
#classmethod
def setup_eager_loading(cls, queryset):
queryset = queryset.only(*cls.Meta.fields)
return queryset
class Meta:
model = Person
fields = ('first_name', 'last_name')
And use it like this:
people = Person.objects.all()
people = SimplePersonSerializer.setup_eager_loading(people)
data = SimplePersonSerializer(people, many=True).data

How to add a custom field in django model form?

I'm trying to add a readonly field in a form.
The model Folder is registered in admin site. The FolderAdminForm defines the custom field statistics. There isn't statistcs field in the Folder model, I just want to put some readonly data on the form. This data is defined in the template.
But I get a error whenever the user doesn't have edit permission. If the user only have the view permission,this error is raised:
AttributeError: Unable to lookup 'statistics' on Folder or FolderAdmin
Here is my code:
class CustomWidget(forms.Textarea):
template_name = 'widget.html'
class FolderAdminForm(forms.ModelForm):
class Meta:
model = Folder
fields = ('field1', 'field2', 'field3',)
statistics = forms.Field(
widget=CustomWidget,
label='Estatísticas',
help_text='Estatísticas da pasta',
)
According to the last part of this answer, you could try to override the forms __init__() method and assign the fields initial attribute.
This could look like:
def __init__(self, *args, **kwargs):
# only change attributes if an instance is passed
instance = kwargs.get('instance')
if instance:
self.base_fields['statistics'].initial = 'something'
super().__init__(*args, **kwargs)
Does this work for you?
The error only occurred whenever I tried to open a folder instance without edit permission (i.e. with read only permission). Therefore, django consider the statistics field as a read only field and then search for a label for this field. Django look for this label in Model, ModelAdmin. Since none of them has the 'statistics' attribute, the error is raised.
So, this worked for me:
class FolderAdminForm(forms.ModelForm):
class Meta:
model = Folder
fields = ('field1', 'field2', 'field3',)
labels = {'statistics': 'Estatísticas'}
statistics = forms.Field(
widget=CustomWidget,
label='Estatísticas',
help_text='Estatísticas da pasta',
)
Therefore, whenever django looks for a label for statistics, it finds this label and the error is not raised. I don't know why it doesn't recognize the label passed as a Field parameter.

what does exclude in the meta class of django mean?

I came across this code:
drinker/models.py:
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
class Drinker(models.Model):
user = models.OneToOneField(User)
birthday = models.DateField()
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
drinker/forms.py:
from django import forms
from django.contrib.auth.models import User
from django.forms import ModelForm
from drinker.models import Drinker
class RegistrationForm(ModelForm):
username = forms.CharField(label=(u'User Name'))
email = forms.EmailField(label=(u'Email Address'))
password = forms.CharField(label=(u'Password'), widget=forms.PasswordInput(render_value=False))
password1 = forms.CharField(label=(u'Verify Password'), widget=forms.PasswordInput(render_value=False))
class Meta:
model = Drinker
exclude = ('user',)
def clean_username(self):
username = self.cleaned_data['username']
try:
User.objects.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError("That username is already taken, please select another.")
def clean(self):
if self.cleaned_data['password'] != self.cleaned_data['password1']:
raise forms.ValidationError("The passwords did not match. Please try again.")
return self.cleaned_data
My Question is about the inner class meta which as two attributes:
model=Drinker
exclude=('user`,)
I have a not-so-clear understanding of how this meta class work. I have read the documentation but I am still confused. Can you kindly explain what those two lines mean and what their purpose is?
Thanks
The exclude attribute tells Django what fields from the model not to include in the form.
Quoting the Selecting fields to use section of the model form documentation:
2. Set the exclude attribute of the ModelForm’s inner Meta class to a list of fields to be excluded from the form.
The model line simply tells Django what model to take the fields from; together the two lines tell Django to give RegistrationForm fields based on all fields on the Drinker model, except 'user'. For the given Drinker model, that's birthday and name.
These fields are added to the other form fields already defined on the form. If the Drinker model gained more fields, those would automatically be part of the form too.
See the Overriding the default fields section of the same chapter:
When you explicitly instantiate a form field like this, it is important to understand how ModelForm and regular Form are related.
ModelForm is a regular Form which can automatically generate certain fields. The fields that are automatically generated depend on the content of the Meta class and on which fields have already been defined declaratively. Basically, ModelForm will only generate fields that are missing from the form, or in other words, fields that weren’t defined declaratively.
The inner Meta class is just a convenient way to create a namespace for such configuration on your form class for the Django framework to find. All Django now has to do is introspect Form.Meta and see what attributes are defined there.
Note that using exclude can lead to security problems. From the same documenation:
It is strongly recommended that you explicitly set all fields that should be edited in the form using the fields attribute. Failure to do so can easily lead to security problems when a form unexpectedly allows a user to set certain fields, especially when new fields are added to a model. Depending on how the form is rendered, the problem may not even be visible on the web page.
The alternative approach would be to include all fields automatically, or blacklist only some. This fundamental approach is known to be much less secure and has led to serious exploits on major websites (e.g. GitHub).
fields = exclude() and fields = '__all__' - means display all the fields
exclude = ('password',) - means exclude password field
fields = ('user','email',) - means display only email field and userfield
in short : fields you want to show up in the form should be mentioned in 'fields' attribute ex:
fields = '__all__' #will show all the fields from the model in the form
'exclude' does the opposite
exclude = ['title'] # don't show the title field

Categories

Resources