SOLUTION AT THE BOTTOM
Problem: Django form populating with list of objects rather than values
Summary: I have 2 models Entities and Breaks. Breaks has a FK relationship to the entity_id (not the PK) on the Entities model.
I want to generate an empty form for all the fields of Breaks. Generating a basic form populates all the empty fields, but for the FK it generates a dropdown list of all objects of the Entities table. This is not helpful so I have excluded this in the ModelForm below and tried to replace with a list of all the entity_ids of the Entities table. This form renders as expected.
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
exclude = ('entity',)
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all().values_list('entity_id', flat=True))
The below FormView is the cbv called by the URL. As the below stands if I populate the form, and for the FK column entity_id choose one of the values, the form will not submit. By that field on the form template the following message appears Select a valid choice. That choice is not one of the available choices.
class ContactFormView(FormView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
My initial thoughts were either that the datatype of this field (string/integer) was wrong or that Django needed the PK of the row in the Entities table (for whatever reason).
So I added a post function to the FormView and could see that the request.body was populating correctly. However I can't work out how to populate this into the ModelForm and save to the database, or overcome the issue mentioned above.
Addendum:
Models added below:
class Entity(models.Model):
pk_securities = models.AutoField(primary_key=True)
entity_id = models.CharField(unique=True)
entity_description = models.CharField(blank=True, null=True)
class Meta:
managed = False
db_table = 'entities'
class Breaks(models.Model):
pk_break = models.AutoField(primary_key=True)
date = models.DateField(blank=True, null=True)
entity = models.ForeignKey(Entity, on_delete= models.CASCADE, to_field='entity_id')
commentary = models.CharField(blank=True, null=True)
active = models.BooleanField()
def get_absolute_url(self):
return reverse(
"item-update", args=[str(self.pk_break)]
)
def __str__(self):
return f"{self.pk_break}"
class Meta:
managed = False
db_table = 'breaks'
SOLUTION
Firstly I got this working by adding the following to the Entity Model class. However I didn't like this as it would have consequences elsewhere.
def __str__(self):
return f"{self.entity_id}"
I found this SO thread on the topic. The accepted answer is fantastic and the comments to it are helpful.
The solution is to subclass ModelChoiceField and override the label_from_instance
class EntityChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.entity_id
I think your problem is two fold, first is not rendering the dropdown correctly and second is form is not saving. For first problem, you do not need to do any changes in ModelChoiceField queryset, instead, add to_field_name:
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all(), to_field_name='entity_id')
Secondly, if you want to save the form, instead of FormView, use CreateView:
class ContactFormView(CreateView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
model = Breaks
In Django, the request object passed as parameter to your view has an attribute called "method" where the type of the request is set, and all data passed via POST can be accessed via the request. POST dictionary. The view will display the result of the login form posted through the loggedin. html.
Related
I have a form that allows the user to pick several vans (many-to-many relationship). Each van has a boolean attribute named "available". I want to only show the vans whose "available" attribute is set to "True". How do I do this in the forms.py file?
I know that this could possibly be done in the template, but I did not want to create a new form-template with each individual field written out. I wanted to know if this functionality could be done in the forms.py file or in the class based view. I believe that doing it that way would be a bit cleaner. I've look into the validators but I don't think this is the way to go. Maybe I need to run a query set in the form file that checks the attribute before passing it to the form template?
views.py
def post(self, request):
"""Take in user data, clean it, and then post it to the database."""
form = self.form_class(request.POST) # pass in the user's data to that was submitted in form
if form.is_valid():
trip = form.save(commit=False) # create an object so we can clean the data before saving it
# now get the clean and normalize the data
first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name']
trip_start = form.cleaned_data['trip_start']
trip_end = form.cleaned_data['trip_end']
van_used = form.cleaned_data['van_used']
trip.save()
forms.py
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
models.py
class trips(models.Model):
class Meta:
verbose_name_plural = "trips"
van_used = models.ManyToManyField(vans)
class vans(models.Model):
class Meta:
verbose_name_plural = "vans"
vanName = models.CharField(max_length=30, unique=True, blank=False)
available = models.BooleanField(default=True, blank=False)
# set up how the vans will be referenced in the admin page
def __str__(self):
return self.vanName
The final form that is rendered would only show the vans whose "available" attribute is set to True. Thanks in advance.
You have to override queryset for van_used field in form like this.
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['van_used'].queryset = vans.objects.filter(available=True)
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
I'm trying to use Django Rest to return a json representation of a model based on a ordering from a custom field that is not attached to the model, but is attached to the serializer. I know how to do this with model specific fields, but how do you use django rest to return an ordering when the field is only within the serializer class? I want to return a list of Pics ordered by 'score'. Thanks!
------Views.py
class PicList(generics.ListAPIView):
queryset = Pic.objects.all()
serializer_class = PicSerializerBasic
filter_backends = (filters.OrderingFilter,)
ordering = ('score')
------Serializer.py
class PicSerializer(serializers.ModelSerializer):
userprofile = serializers.StringRelatedField()
score = serializers.SerializerMethodField()
class Meta:
model = Pic
fields = ('title', 'description', 'image', 'userprofile', 'score')
order_by = (('title',))
def get_score(self, obj):
return Rating.objects.filter(picc=obj).aggregate(Avg('score'))['score__avg']
You could move the logic of the method get_score to the manager of the class Pic. In this answer there is an example of how to do it.
Once you have it in the manager, the score field would become "magically" available for every object of the class Pic everywhere (serializer, views...) and you'll be able to use it for ordering.
Models.py:
class Comment(models.Model):
user = models.ForeignKey(User)
document = models.ForeignKey(Document)
section = models.ForeignKey(Section, null=True, blank=True)
description = models.TextField(null=True, blank=True)
Forms.py:
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ('section', 'description')
Each Comment belongs to a Section of a Document. Each Document hasmany Sections. However, the ModelChoiceField printed out by Django will contain Sections for ALL Documents.
How do I tell Django to only print the Sections that belong to a particular Document?
I looked at ModelFormSets - Changing the queryset but I don't think it's quite what I'm after.
If all you need to do is adjust the admin site you can override the formfield_for_foreignkey method on your django admin class.
From the docs:
ModelAdmin.formfield_for_foreignkey(self,db_field, request, **kwargs)
The formfield_for_foreignkey method on a ModelAdmin allows you to override the
default formfield for a foreign key field. For example, to return a subset
of objects for this foreign key field based on the user:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This uses the HttpRequest instance to filter the Car
foreign key field to only display the cars owned by the User instance.
I think you want to change the ModelChoiceField's queryset and not the queryset of the formset.
Here's my setup:
from django.contrib.auth.models import User
class Product(models.Model):
...
email_users = models.ManyToManyField(User, null=True, blank=True)
...
[elsewhere]
class ProductAdmin(admin.ModelAdmin):
list_display = ('name','platform')
admin.site.register(Product, ProductAdmin)
My main problem is, when I'm viewing the "Product" page in the admin section, email users are not being being ordered by their ID by default, and I'd like that to be ordered by their username.
From what I've read so far, it seems like I need to be adding:
email_users.admin_order_field = 'xxxx'
But I'm not quite sure what the syntax is to access the username.
The answer was referred to in Hao Lian's comment above, essentially, this is what needed to be done:
class ProductAdminForm(ModelForm):
email_users = forms.ModelMultipleChoiceField(queryset=User.objects.order_by('username'))
class Meta:
model = Product
class ProductAdmin(admin.ModelAdmin):
list_display = ('name','platform')
form = ProductAdminForm
admin.site.register(Product, ProductAdmin)
Mine was slightly different in the sense that I required the forms.ModelMultipleChoiceField, whereas the answer provided used forms.ModelChoiceField()
Solution above works well, but in my case I lost all attributes and customizations that my component had by default (like required, label, etc).
In some cases could be better override __init__() method in order to customize only your queryset, nothing else will change.
class ProductAdminForm(ModelForm):
class Meta:
model = Product
fields = '__all__' # required in new versions
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['email_users'].queryset = (
self.fields['email_users'].queryset.order_by('username')
)