Get objects that are not in manytomany relationship of a different model? - python

Lets say I have two models and a form:
class Person(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
class Car(models.Model):
plate = models.CharField(max_length=255)
persons = models.ManyToManyField(Person)
class CarAddForm(forms.ModelForm):
plate = forms.CharField()
persons = forms.ModelMultipleChoiceField(queryset=Person.objects.all())
class Meta:
model = Car
fields = [
'plate',
'persons'
]
Is there a way to get ModelMultipleChoiceField queryset of people that are NOT associated with any car?
In case of editing Car model object, the queryset should contain people that are NOT associated with any car PLUS people that are associated with the car being edited
PS: maybe there is a better way to achieve this?

You can make use of the limit_choices_to--(Doc) argument of ManyToManyField as
class Car(models.Model):
plate = models.CharField(max_length=255)
persons = models.ManyToManyField(
Person,
limit_choices_to={"car__isnull": True}
)
Alternatively, you can also alter the queryset argument of ModelMultipleChoiceField as
class CarAddForm(forms.ModelForm):
plate = forms.CharField()
persons = forms.ModelMultipleChoiceField(
queryset=Person.objects.filter(car__isnull=True)
)
class Meta:
model = Car
fields = [
'plate',
'persons'
]

You can specify a filter for the query:
from django.db import models
class CarAddForm(forms.ModelForm):
...
persons = forms.ModelMultipleChoiceField(
queryset=Person.objects\
.annotate(car_count=models.Count('cars'))\
.filter(car_count=0))
...
Another options is to override the forms __init__() method. Maybe like this:
from django.db import models
class CarAddForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['person'].queryset = self.fields['person'].queryset\
.annotate(car_count=models.Count('cars'))\
.filter(car_count=0))

Related

Correct Django serializer implementation for many to many field

I have two django models that are related with a many to many field:
class Member(models.Model):
user = models.OneToOneField(to=settings.AUTH_USER_MODEL)
date_of_birth = models.DateField()
bio = models.TextField()
class Book(models.Model):
name = models.CharField(max_length=255)
author= models.CharField(max_length=255)
description = models.TextField()
read_by = models.ManyToManyField(to=Member, related_name='books_read')
The serializers for these models are:
class MemberSerializer(serializers.Model):
id = serializers.IntegerField(read_only=True)
user_id = serializers.IntegerField(read_only=True)
class Meta:
model = Member
fields = ['id', 'user_id', 'date_of_birth', 'bio']
class BookSerializer(serializers.Model):
id = serializers.IntegerField(read_only=True)
class Meta:
model = Book
fields = ['id', 'name', 'author', 'bio']
I want to create an endpoint to be able to add a book to a member. The only way I could write a serializer for it was:
class BookIdSerializer(serializers.Model):
class Meta:
model = Book
fields = ['id']
def update(self, **kwargs):
# logic to add book with passed id to the authenticated user's member profile
This however feels very wrong for two obvious reasons:
1 - There is an entire serializer object just to receive a book id
2 - It is not even generic because it performs a very specific function of adding a book with passed book id to a member
I am sure there is a better way of doing this. Kindly guide me if you know.
You can use a PrimaryKeyRelatedField:
class MemberSerializer(serializers.Model):
id = serializers.IntegerField(read_only=True)
user_id = serializers.IntegerField(read_only=True)
books = serializers.PrimaryKeyRelatedField(many=True, queryset=Book.objects.all())
class Meta:
model = Member
fields = ['id', 'user_id', 'date_of_birth', 'bio', 'books']
This will place the related books in an array, and you can edit that array to add and remove books, allowing adding or removing many at a time.
See How to use PrimaryKeyRelatedField to update categories on a many-to-many relationship for a related question/answer.

Django - UniqueConstraint not created

I am trying to enforce a constraint for mysql where user is prohibited from inserting twice the same name and model. E.g This should not be allowed to be inserted twice: name:Name1 model:Model1
#Model
class Car(models.Model):
name = models.CharField(max_length=100)
model = models.CharField(max_length=100)
#View
class CarListCreateAPIView(generics.ListCreateAPIView):
serializer_class = CarSerializer
def get_queryset(self):
trip_code = self.kwargs.get("pk")
return Car.objects.filter(trip = trip_code) #Return cars for given trip
#Seializer
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('__all__')
constraints = [
models.UniqueConstraint(fields=['name', 'model'], name='car_name_model_constraint')
]
The problem is that the constraint is never created and thus not enforced. What might be the issue with the code?
Use unique_together in class Meta in Model like that:
class Car(models.Model):
name = models.CharField(max_length=100)
model = models.CharField(max_length=100)
class Meta:
unique_together = ['name','model']
More on that here: https://docs.djangoproject.com/en/4.0/ref/models/options/#unique-together

Lack of data with serialization model with ManyToManyField

Here's are examples I have:
models.py:
class Example(models.Model):
title = models.CharField(...)
description = models.CharField(...)
class Foo(models.Model):
example = models.ManyToManyField(Example)
serializers.py:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = '__all__'
depth = 1
views.py:
...
serialized_data = [FooSerializer(foo).data for foo in Foo.objects.all().get]
In output, I receive only Example's IDs, but is there any way I could get title and description fields also (details of m2mfield)? As I understand, Foo.objects.all().get simply doesn't contain this data, but maybe I could somehow get it and use it?
I could also rebuild models if needed, but currently I use m2mf because of needs to contain multiple objects as related to this model data.
update
models.py:
class Event(models.Model):
ts = models.BigIntegerField(editable=False)
class Foo(Event):
user = models.ForeignKey(User, ...)
example = *...(remains to be the same)*
foos = models.ForeignKey('self', **somemore** null=True)
serializers.py:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = '__all__'
def to_representation(self, instance):
result = {'ts': instance.ts}
if isinstance(instance, Foo):
result['foo'] = FooSerializer(instance).data
return result
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
class FooSerializer(serializers.ModelSerializer):
# user = UserSerializer(read_only=True) # with this I have an error: Got AttributeError when attempting to get a value for field 'username' on #serializer 'UserSerializer'
class Meta:
model = Foo
fields = '__all__'
depth = 1
You could use depth attribute to achieve desired output.
The default ModelSerializer uses primary keys for relationships, but
you can also easily generate nested representations using the depth
option.The depth option should be set to an integer value that
indicates the depth of relationships that should be traversed before
reverting to a flat representation.
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = '__all__'
depth = 1
Apart from the answer, I would like to change your views.py code, cause it seems like very bad :(. Do it on DRF Way as
serialized_data = FooSerializer(Foo.objects.all(), many=True).data<br>
Example View
from rest_framework.viewsets import ModelViewSet
class FooViewset(ModelViewSet):
serializer_class = FooSerializer
queryset = Foo.objects.all()
UPDATE-1
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
exclude = ('password',) # add fields that are need to be excluded
class FooSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Foo
fields = '__all__'
depth = 1
depth = 1 will serializer all fields in the model, (It's same as setting the fields=='__all__' in Meta class of serializer)
UPDATE-2
class FooSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Foo
fields = '__all__'
depth = 1
def to_representation(self, instance):
real_data = super().to_representation(instance).copy()
# DO YOUR EXTRA CHECKS
child = UserSerializer(instance.child_foo).data
if child:
real_data.update({"child_data": child})
# After your checks, add it to "real_data"
return real_data
and I assumed I have a Foo model as
class Foo(models.Model):
example = models.ManyToManyField(Example)
user = models.ForeignKey(User)
child_foo = models.ForeignKey('self', null=True, blank=True)
In your serializer add depth = 1. Example where 'users' is the related field:
FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ('id', 'account_name', 'users', 'created')
depth = 1

In the Django admin page for a given model, is it possible to create one or more models that have the original given model as a foreign key field?

For example, let's say we have Cars and Manufacturers, where the Cars model contains a foreign key to Manufacturers (many cars to one manufacturer). In the Manufacturer admin page, when you are creating a new Manufacturer, you get to input whatever fields a manufacturer might have, such as name or address for example.
Is there a way to also be able to create a new Car belonging to the given Manufacturer on the Manufacturer admin page? For instance, you would be prompted to select a Car on the Manufacturer page, or have the ability to create one if the one you want does not exist. If this is possible, is there a way to create an arbitrary amount of cars that belong to this manufacturer this way?
As long as your car has foreign key to manufacturer, you should be able to choose in Django Admin
in your models:
from django.db import models
class Manufacturer(models.Model):
name = models.CharacterField(max_length=200,blank=True,null=True)
location = models.CharacterField(max_length=200,blank=True,null=True)
class Meta:
verbose_name = 'Manufacturer'
verbose_name_plural = 'Manufacturers'
def __str__(self):
return name
class Car(models.Model):
manufacturer= models.ForeignKey(Manufacturer,null=True)
model = models.CharacterField(max_length=200,blank=True,null=True)
class Meta:
verbose_name = 'Car'
verbose_name_plural = 'Cars'
def __str__(self):
return model
In your admin.py:
from django.contrib import admin
from models import Manufacturer
from models import Car
class ManufacturerAdmin(admin.ModelAdmin):
fieldsets = ((None, {'fields': ['name','location']}),)
class Meta:
verbose_name = 'Manufacturer'
verbose_name_plural = 'Manufacturers'
class CarAdmin(admin.ModelAdmin):
fieldsets = ((None, {'fields': ['manufacturer','model']}),)
class Meta:
verbose_name = 'Car'
verbose_name_plural = 'Cars
admin.site.register(Manufacturer,ManufacturerAdmin)
admin.site.register(Car,CarAdmin)
This should do it.

Key error in work with serializer

models.py:
class Car(models.Model):
# many fields
class CarOptions(models.Model):
car = models.OneToOneField(Car, primary_key=True, related_name='options')
color = models.CharField()
# many other fields
So, I want get all information from Car and its CarOptions.
serializer.py:
class CarOptionsSerializer(serializers.ModelSerializer):
color = serializers.CharField()
class Meta:
model = CarOptions
fields = ('color')
class CarSerializer(serializers.ModelSerializer):
color = CarOptionsSerializer(many=True, read_only=True, )
class Meta:
model = Car
fields = ('many fields', 'color', )
In views.py I have created class based on XMLRenderer, in _to_xml() method (link) I use:
self._to_xml(xml, item["color"])
But it does not work. I have an error:
KeyError: 'color'
I print item-dict and there is no key color in it.
How to fix that?
Thanks!
You should reference the options in the CarSerializer using the options field name rather than the color field name (as you set the related field to be options in the OneToOneField). You do not define a color field directly on the Car.
The following code works for me with the latest django and rest framework:
models.py
class CarModel(models.Model):
name = models.CharField(max_length=250)
class CarOptionsModel(models.Model):
car = models.OneToOneField(CarModel, related_name='options')
color = models.CharField(max_length=250)
serializers.py
class CarOptionsSerializer(serializers.ModelSerializer):
color = serializers.CharField()
class Meta:
model = CarOptionsModel
fields = ('color',)
class CarSerializer(serializers.ModelSerializer):
options = CarOptionsSerializer(read_only=True, )
class Meta:
model = CarModel
fields = ('options', 'name')
views.py
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = CarModel.objects.all()
urls.py
router = routers.DefaultRouter()
router.register(r'api', CarViewSet)
urlpatterns = router.urls
I would note that since you define a one to one mapping, you cannot have many=True.

Categories