Django. How to validate and save many models with OneToOne relation? - django

I have the model( this just example in real life model is much bigger):
class Ride(models.Model):
account = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='dives')
date = models.DateField(blank=True, null=True)
referenceA = models.ForeignKey(
RefA,
related_name="rides",
blank=True,
null=True
)
# in real life there is much more options and group of option
optionA = models.FloatField(
blank=True, null=True
)
optionB = models.FloatField(
blank=True, null=True
)
I have divided this model like this:
class Ride(models.Model):
account = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='dives')
date = models.DateField(blank=True, null=True)
referenceA = models.ForeignKey(
RefA,
related_name="rides",
blank=True,
null=True
)
ride_options = models.OneToOneField(
RideOption
)
class RideOption(models.Models):
optionA = models.FloatField(
blank=True, null=True
)
optionB = models.FloatField(
blank=True, null=True
)
Now I want to create a page which edit Ride model instance with all related model instances(RideOption, ...).
I prefer to use ModelForm for each model but how can I validate it all together.
I can write this validation in view, something like this:
ride_form = RideModelForm(...)
ride_option_form = RideOptionModelForm(...)
if ride_option_form.is_valid():
if ride_form.is_valid():
# now save
but as for me it's really ugly and I can have a lot of related model.
Is there a way to hide this validation and saving internal?
I looked on FormSet but as I understand they works only for Models with Foreign relation.
Maybe somebody knows how to solve this with formset?
Or another(not ugly) way to do that?

It sounds like you want an Inline Formset
"Inline formsets is a small abstraction layer on top of model formsets. These simplify the case of working with related objects via a foreign key."
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#inline-formsets
A OneToOneField is just a special case of ForeignKey and ought to work the same.
eg
# forms.py
from django.forms.models import inlineformset_factory
from .models import Ride, RideOption
RideFormSet = inlineformset_factory(Ride, RideOption, extra=0)
# views.py
from django.shortcuts import get_object_or_404
from .forms import RideFormSet
from .models import Ride
def myview(request, ride_id):
ride = get_object_or_404(Ride, pk=ride_id)
formset = RideFormSet(data=request.POST or None, instance=ride)
if formset.is_valid():
formset.save()
# maybe redirect to success url here
# else render template here

Related

Django Rest Framework not validating all model's fields

I searched for this problem everywhere without being able to find an answer though it seems basic DRF usage, so I might be missing sth.
I have a Customer model with certain required fields:
from django.db import models
from django.utils.translation import gettext_lazy as _
from applications.core.models.country import Country
from applications.core.models.customer_states.customer_state import \
CustomerState
class Customer(models.Model):
class Meta:
verbose_name = _('customer')
verbose_name_plural = _('customers')
user_email = models.EmailField(_('email'), max_length=100, unique=True, default=None)
complete_name = models.CharField(_('complete name'), max_length=200, default=None)
phone = models.CharField(_('phone'), max_length=50, default=None)
country = models.ForeignKey(Country, models.PROTECT, verbose_name=_('country'), default=None)
city = models.CharField(_('city'), max_length=100, default=None)
city_state = models.CharField(_('city state'), max_length=100, default=None)
address = models.CharField(_('address'), max_length=100)
zip_code = models.CharField(_('zip code'), max_length=50, default=None)
customer_state = models.OneToOneField(CustomerState, models.PROTECT)
notes = models.TextField(_('notes'), max_length=200, blank=True, null=True)
And I have this serializer:
from rest_framework import serializers
from applications.core.models.customer import Customer
from applications.core.models.customer_states.implementations.pending_manual_validation_state import \
PendingManualValidationState
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = '__all__'
def to_internal_value(self, data):
self.add_default_state_if_missing(data)
return super(CustomerSerializer, self).to_internal_value(data)
#staticmethod
def add_default_state_if_missing(data):
data['customer_state'] = PendingManualValidationState.objects.create().pk
Though I have explicitly told DRF that it should use all model's fields it does not seem to check for the requirement of fields like 'address' and whenever I create the serializer with data missing 'address' and call serializer.is_valid() it returns True.
Why?
Found the answer by myself:
It would seem that default=None in the field makes DRF realize that even if the field is required, not receiving data for it is not a problem.
I set those default because otherwise Django would set a '' default value and thus, PostgreSQL would not raise exceptions for those empty data fields. But now that I am starting to use DRF for validations I no longer need those default.
Yet, if I happen to create and save a Customer without using the serializer, then I risk letting Django create a field with a default '' without having PostgreSQL complain about it. I figure that risk has a low probability though.

django select records from multiple tables with same foreign key

I would like to execute a single query in Django which retrieves related data, by foreign key, in multiple tables. At present I have to run a query on each table e.g. (House, Furniture, People) using the House number as a filter.
In SQL I can do this in one query like this:
SELECT house.number, house.number_of_rooms, furniture.type, people.name
FROM (house INNER JOIN furniture ON house.number = furniture.house_number)
INNER JOIN people ON house.number = people.house_number
WHERE (((house.number)="21"));
Can this be done in Django?
See example models below:
class House(models.Model):
number = models.CharField('House Number', max_length=10, blank=True, unique=True, primary_key=True)
number_of_rooms = models.IntegerField(default=1, null=True)
class Furniture(models.Model):
house_number = models.ForeignKey(House, on_delete=models.CASCADE, null=True)
type = models.CharField('Furniture Type', max_length=50)
class People(models.Model):
house_number = models.ForeignKey(House, on_delete=models.CASCADE, null=True)
first_name = models.CharField('First Name', max_length=50)
In your models add related_name arguments for foreign keys, so that you can retrieve the objects related to the House() instance.
class Furniture(models.Model):
house_number = models.ForeignKey(House, related_name='house_furniture', on_delete=models.CASCADE, null=True)
type = models.CharField('Furniture Type', max_length=50)
class People(models.Model):
house_number = models.ForeignKey(House, related_name='house_people', on_delete=models.CASCADE, null=True)
first_name = models.CharField('First Name', max_length=50)
Then run the migration using following commands.
python manage.py makemigrations
python manage.py migrate
Then create a new serializers.py module in the same app.
#import models Furniture, People, house
from rest_framework import serializers
class FurnitureSerializer(serializer.ModelSerializer):
class Meta:
model = Furniture
fields = ['type'] # if you want all the fields of model than user '__all__'.
class PeopleSerializer(serializer.ModelSerializer):
class Meta:
model = People
fields = ['first_name'] # if you want all the fields of model than user '__all__'.
class HouseSerializer(serializer.ModelSerializer):
house_furniture = FurnitureSerializer(many=True)
house_people = PeopleSerializer(many=True)
class Meta:
model = Furniture
fields = ['number', 'number_of_rooms', 'house_furniture', 'house_people']
Now, in your views.py you can simply query on model House and serializer the result with HouseSerializer().
#import models from models.py
#import serializer from serializers.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.generics import ListAPIView
class ListHouseView(ListAPIView):
serializer_class = HouseSerializer
queryset = House.objects.filter() #here you can apply filters on the fields of house model and user using related_name you can filter on other related models as well.
Now, simply call ad this in your app's urls.py
url_pattern = [
path('list-house/', ListHouseView.as_view()),
]
Make sure that have a path in your project's urls.py to reach this app's urls.py.
The usual Django way of dealing with this is Queryset.prefetch_related() and iterating through Python (unless you're using Postgres, which has its own solution of ArrayAgg). Given your models, it'll cost three queries, but you won't have to deal with de-normalized row results.
h = House.objects.prefetch_related('furniture_set', 'people_set').get(number='21')
for furniture in house.furniture_set.all():
print(furniture)
for person in house.people_set.all():
print(people)
prefetch_related() caches the results and does the "joining" in Python once the queryset is evaluated, so iterating through the reverse relationships won't incur additional queries, and you're free to structure/serialize the data however you like. The raw SQL from this is something like:
SELECT house.number, house.number_of_rooms FROM house WHERE house.number = '1'
SELECT furniture.id, furniture.house_number_id, furniture.type FROM furniture WHERE furniture.house_number_id IN ('1')
SELECT people.id, people.house_number_id, people.first_name FROM people WHERE people.house_number_id IN ('1')
But Django does that behind-the-scenes so that you can just deal with a model instance in Python.

how to make a form field exact?

In django how to make form field exact, i.e it will have choices?
My forms.py:
from django import forms
class FilterForm(forms.Form):
category = forms.CharField()
price = forms.IntegerField()
My models.py:
CATEGORY_CHOICES = (
('Fruits and Vegetables', 'Fruits and Vegetables'),
('Electronics', 'Electronics'),
('Clothing', 'Clothing'),
('Books', 'Books'),
)
class Item(models.Model):
title = models.CharField(max_length=120)
price = models.FloatField()
discount_price = models.FloatField(blank=True, null=True)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=120, null=True, blank=True)
image_url = models.CharField(max_length=2083, null=True, blank=True)
slug = models.SlugField(null=True, blank=True)
description = models.TextField(null=True, blank=True)
Please make use of a ModelForm [Django-doc]. A ModelForm is capable of automating a lot of aspects when creating or update model records. Furthermore it can automatically construct the fields based on the fields of the model. You can, if you want to, alter the widgets, etc. But usually a ModeLField is a good starting point.
Here you thus can construct a form like:
# app/forms.py
from django import forms
from app.models import Item
class FilterForm(forms.ModelForm):
class Meta:
model = Item
fields = ['category', 'price']
Where you replace app with the name of the app.
You can use ModelForm
from django import forms
from .models import Item
class FilterForm(forms.ModelForm):
class Meta:
model = Item
fields = [
'category',
'price'
]
If you wanna stick with Form, use Choice Field and copy the Choices in form
from django import forms
class FilterForm(forms.Form):
CATEGORY_CHOICES = (
('Fruits and Vegetables', 'Fruits and Vegetables'),
('Electronics', 'Electronics'),
('Clothing', 'Clothing'),
('Books', 'Books'),
)
category = forms.ChoiceField(choices=CATEGORY_CHOICES)
price = forms.IntegerField()

Django - Using forms.CheckboxSelectMultiple

I'm trying to create a multi-select list of checkboxes for names in a database.
I thought I could do the below in the from, and do some kind of loop in the template to render each name, but noting I try works. Is this the correct approach, any clues on how to render this in the template?
Thanks
from django import forms
from .models import Player
class PlayerForm(forms.Form):
team = forms.MultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
choices=[Player.objects.all()]
)
from django.db import models
class Player(models.Model):
lname = models.CharField(max_length=10, verbose_name='Last Name')
fname = models.CharField(max_length=10, verbose_name='First Name')
wins = models.SmallIntegerField(default=0, null=True, blank=True)
loss = models.SmallIntegerField(default=0, null=True, blank=True)
def __str__(self):
return "{}".format(self.lname)
class Meta:
ordering = ['lname']
Not entirely. If you need to select a option among model options, you should use a ModelMultipleChoiceField field [Django-doc]. This will not only make it more convenient to work with data, but it will furthermore each time query the database, such that, if you add a new Player, one can select that one.
You thus can implement this as:
class TeamForm(forms.Form):
team = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Player.objects.all()
)
It might furthermore be better to name your form TeamForm, since you here do not create/update/... a Player, but you select a team.

Django Admin custom foreign key select box

I want to customize Django admin select box and show thumbnail in the select box next to the image title
I have a class called Image and another class called News, that has a foreign key to the Image.
Note: I use Django jet as admin template.
class Image(models.Model):
alternate = models.CharField(
verbose_name=_('Alternate'),
max_length=255,
null=True,
blank=True
)
title = models.CharField(
verbose_name=_('Title'),
max_length=255,
null=True,
blank=True
)
artist = models.ManyToManyField(
'Artist',
verbose_name=_('Artist'),
blank=True
)
image = models.ImageField()
def __str__(self):
return "({}) {}".format(self.pk, self.title)
class Meta:
verbose_name = _('Image Attachment')
verbose_name_plural = _('Image Attachments')
#staticmethod
def autocomplete_search_fields():
return 'title',
class News(BaseModel):
title = models.CharField(
verbose_name=_('Title'),
max_length=255,
null=True,
blank=True
)
summery = RichTextField(
verbose_name=_('Summery'),
null=True,
blank=True,
)
main_image = models.ForeignKey(
Image,
verbose_name=_('Main Image'),
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='images'
)
Now I want to show the thumbnail of the image in choices in Django admin when I want to add news.
Now my select box look like this
You will need to create a custom widget that inherits from Select, the most important part it seems will be setting the option_template_name to be a template that you create to show the image. Since you are using something other than the base Django Admin, you may want to look into extending the widgets in that Library.
Something along the lines of:
class SelectWithImage(Select):
...
option_template_name = 'myapp/forms/widgets/select_option_with_image.html'
...
Then adjust the admin formfield_overrides for the News model in your admin.py as described here and you should be good to go!
This step will look something like this:
from django.contrib import admin
from django.db import models
# Import our custom widget and our model from where they're defined
from myapp.models import News
from myapp.widgets import SelectWithImage
class NewsAdmin(admin.ModelAdmin):
formfield_overrides = {
models.ForeignKey: {'widget': SelectWithImage},
}