I am trying to sort out a specific problem that involve "many2many" relationship using through specification.
I've already tried to use inline_factory but I was not able to sort out the problem.
I have these tables
class Person(models.Model):
id = models.AutoField(primary_key=True)
fullname = models.CharField(max_length=200)
nickname = models.CharField(max_length=45, blank=True)
class Meta:
db_table = 'people'
class Role(models.Model):
role = models.CharField(max_length=200)
class Meta:
verbose_name_plural = 'roles'
db_table = 'roles'
class Study(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
description = models.CharField(max_length=1000)
members = models.ManyToManyField(Person, through='Studies2People')
class Meta:
db_table = 'studies'
class Studies2People(models.Model):
person = models.ForeignKey(Person)
role = models.ForeignKey(Role)
study = models.ForeignKey(Study)
class Meta:
verbose_name_plural = 'studies2people'
db_table = 'studies2people'
unique_together = (('person', 'role', 'study'),)
#forms.py
from .models import Study, Person, Role, Studies2People
class RegisterStudyForm(ModelForm):
class Meta:
model = Study
fields = '__all__'
#View.py
class StudyCreateView(CreateView):
template_name = 'managements/register_study.html'
model = Study
form_class = RegisterStudyForm
success_url = 'success/'
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
The code above creates a form like:
Study.Title
Study.description
List of People
I want to create a form to fill in all fields that involve Studies2People Something like this:
Study.Title
Study.description
Combo(people.list)
Combo(Role.list)
Maybe I should start from Studies2People but I don't know how to show the "inline" forms involved.
Thanks in advance
C.
waiting someone that is able to explain with some examples the relationship m2m with through (model & view), I sorted out my problem in a different way.
I've created three forms.
1 Model Form (study)
2 Form (forms with ModelChoiceField(queryset=TableX.objects.all())
Created a classView to manage the get and post action.(validation form too)
In the post procedure I used "transaction" to avoid "fake" data.
I hope that someone will post an example with complex m2m relationships.
Regards
Cinzia
Related
i'm working on a django project and i got this error (Cannot assign "'11'": "Product.category" must be a "CategoryProduct" instance.) anyone here can help me please.
Model:
class Product(models.Model):
name = models.CharField("Nombre", max_length=150)
category = models.ForeignKey(CategoryProduct, on_delete=models.SET_NULL, null=True, related_name='category')
def __str__(self):
return self.name
View:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
def post(self, request, *args, **kwargs):
form = self.get_form()
category = CategoryProduct.objects.get(id=request.POST['category'])
if form.is_valid():
product = form.save(commit=False)
product.category = category
product.save()
Form:
class ProductForm(forms.ModelForm):
name = forms.CharField(max_length=150, label="Nombre")
category = forms.ChoiceField(choices=[(obj.id, obj.name) for obj in CategoryProduct.objects.all()], label="Categoría")
class Meta:
model = Product
fields = ['name', 'category']
You can let Django's ModelForm do its work, this will create a ModelChoiceField [Django-doc], which is where the system gets stuck: it tries to assign the primary key to category, but that should be a ProductCategory object, so you can let Django handle this with:
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'category']
If you want to specify a different label, you can use the verbose_name=… [Django-doc] from the model field, or specify this in the labels options [Django-doc] of the Meta of the ProductForm. So you can specify Categoria with:
class Product(models.Model):
name = models.CharField('Nombre', max_length=150)
category = models.ForeignKey(
CategoryProduct,
on_delete=models.SET_NULL,
null=True,
related_name='products',
verbose_name='Categoria'
)
def __str__(self):
return self.name
then the CreateView can just use its boilerplate logic:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the Category model to the Product
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the category relation to products.
Let us imagine that I have two models.
First model contains curse details and user that created this course
class Course(models.Model):
course_name = models.CharField(max_length=100, null=False)
description = models.CharField(max_length=255)
user_profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
and my second model is:
class Lesson(models.Model):
course = models.OneToOneField(Course, on_delete=models.CASCADE) #
# inside the course I want my APIVIEW to list only the courses that current user created.
# OnetoOne relationship does not solve the problem.
status = models.CharField(choices=STATUS, null=False, default=GOZLEMEDE,max_length=20)
tariffs = models.FloatField(max_length=5,null=False,default=0.00)
continues_off = models.CharField(max_length=2)
user_profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
My serializers for both Models:
class LessonSerializer(serializers.ModelSerializer):
class Meta:
model = models.Lesson
fields = ('course', 'status', 'tariffs', 'continues_off', 'user_profile')
def create(self, validated_data):
lesson = models.Lesson.objects.create(
course = validated_data['course'],
status = validated_data['status'],
tariffs=validated_data['tariffs'],
continues_off=validated_data['continues_off'],
user_profile=validated_data['user_profile']
)
return lesson
class CourseSerializer(serializers.ModelSerializer):
"""Serializers Course content"""
class Meta:
model = models.Course
fields = '__all__'
def create(self,validated_data):
course = models.Course.objects.create(
course_name = validated_data['course_name'],
description=validated_data['description'],
user_profile=validated_data['user_profile']
)
return course
My Viewset:
class LessonViewset(viewsets.ModelViewSet):
model = models.Lesson
serializer_class = serializers.LessonSerializer
authentication_classes = (SessionAuthentication,)
permission_classes = (IsAuthenticated,BasePermission,)
def get_queryset(self):
user_current = self.request.user.id
return models.Lesson.objects.filter(user_profile=user_current)
How can I get the desired result. I want to get the courses for the current user and show them as a dropdown list in my API view. Just only the courses that user created should be in the dropdown list not all.
OnetoOne relationship gives all results of course table.
i think change your view code to :
def get_queryset(self,id):
return model.objects.filter(user_profile=id)
#You do not need to call it again when you put the Lesson on the model
\
How can I set values on to a ManyToManyField which specifies an intermediary model?
In the models.py
class BookUser(models.Model):
email = models.EmailField()
class Book(models.Model):
author_id= models.CharField(max_length=255)
send_to = models.ManyToManyField(BookUser, through='BookUserRelationship')
book_id = models.AutoField(primary_key=True)
file_size = models.CharField(null=True)
class BookUserRelationship(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
user = models.ForeignKey(BookUser, on_delete=models.CASCADE)
shared_date = models.DateTimeField(auto_now_add=True,null=True,blank=True)
Tried to update in serializers.py
class BookSerializer(serializers.ModelSerializer):
send_to = BookUserSerializer(many=True, read_only=True)
class Meta():
model = Book
fields = ('book_id', 'author_id','file_size','send_to')
class BookUserSerializer(serializers.ModelSerializer):
model = BookUser
fields = ('email')
In the views.py for listing the books by passing the book_id as query params
class BookListView(generics.ListCreateAPIView):
serializer_class = serializers.BookSerializer
def get(self, request, *args, **kwargs):
user = self.request.user
book_id = self.request.query_params.get('book_id', None)
if book_id:
book = models.Book.objects.filter(book_id=book_id)
return Response(serializers.BookSerializer(book[0]).data)
You don't need to do anything at all. You have already set the relevant data in your for loop, by creating the BookUserRelationship instances. That is the many-to-many relationship; you should just remove the instance.send_to.set(emails) line.
I use django rest in my project and until now for list of objects I used ListSerializer, when I needed to have min length and max length of list I googled and reached to ListField.
Before that my code worked fined without any error and misbehavior. Now I use ListField for my list field serializer, But I didn't get when to use ListSerializer? Can someone explain the difference between ListSerializer and FieldSerializer?
My sample code with ListSerializer:
tags = serializers.ListSerializer(child=serializers.CharField(allow_blank=False), required=False)
My sample code with ListField:
open_hour = serializers.ListField(child=serializers.DictField(), max_length=7, min_length=7)
Disclaimer: This answer is not complete
Can someone explain the difference between ListSerializer and
FieldSerializer?
I assume the question is difference between serializers.ListSerializer and serializers.ListField
Suppose we have two models as
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
def __str__(self):
return f'{self.name} : {self.artist}'
and serializer as
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
class MusicianSerializer(serializers.ModelSerializer):
AlbumSerializer(many=True, source='album_set')
class Meta:
fields = '__all__'
model = Musician
ListSerializer
As stated in official DRF doc
When a serializer is instantiated and many=True is passed, a
ListSerializer instance will be created. The serializer class then
becomes a child of the parent ListSerializer
For example, we could re-write the MusicianSerializer with ListSerializer as
class MusicianSerializer(serializers.ModelSerializer):
albums = serializers.ListSerializer(child=AlbumSerializer(), source='album_set')
class Meta:
fields = '__all__'
model = Musician
it would produce the results same as before. But, if we are trying to use ListField instead of ListSerializer It will raise an error
'RelatedManager' object is not iterable
When I checked the source code, I found that both ListSerializer and ListField are inherited from the same class (parent and grand parent are same)
I ran into this same problem and I believe I found a solution!
The trick is you need to create a new Serializer that inherits the ListSerializer class and override the to_representation() method to output your desired format.
If you look at the DRF source code for ListSerializer you can see the default to_representation() method looks like the following...
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, models.Manager) else data
return [
self.child.to_representation(item) for item in iterable
]
Example
models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
def __str__(self):
return f'{self.name} : {self.artist}'
serializers
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
class AlbumKeyValueSerializer(serializers.ListSerializer):
def to_representation(self, data):
reaction_count_set = {}
for item in data.all():
reaction_count_set[item.name] = item.artist
return reaction_count_set
class MusicianSerializer(serializers.ModelSerializer):
AlbumKeyValueSerializer(child=AlbumSerializer(), source='album_set')
class Meta:
fields = '__all__'
model = Musician
I have a fairly basic model that allows users to create posts of different 'types'. There's currently a Text type and a Photo type that inherits from a base 'Post' type.
I'm currently pulling TextPosts and PhotoPosts and chaining the two QuerySets, but this seems like a bad idea.
Is there a way to simply query for both types of posts at once? The reason I'm not using .filter() on Post itself is because I (presumably) don't have any way of getting the TextPost or PhotoPost object from it (or do I?)
PS: Does it make more sense to call it BasePost or Post if I'll never be using Post by itself?
class Post(AutoDateTimeModel):
POST_TYPES = (
# Linkable Social Networks
('TEXT', 'Text'),
('PHOTO', 'Photo'),
('LINK', 'Link'),
)
post_type = models.ForeignKey(ContentType)
user = models.ForeignKey(User, blank=True, null=True)
interests = models.ManyToManyField(Interest, related_name='interests')
class Meta:
app_label = 'posts'
ordering = ('-created_at',)
def save(self, *args, **kwargs):
if not self.pk:
self.post_type = ContentType.objects.get_for_model(type(self))
# import pdb; pdb.set_trace()
super(Post, self).save(*args, **kwargs)
class TextPost(Post):
""" Text post model """
body = models.TextField()
class Meta:
app_label = 'posts'
class PhotoPost(Post):
""" Photo post model. This can contain multiple photos. """
description = models.TextField()
class Meta:
app_label = 'posts'
class Photo(models.Model):
""" Individual image model, used in photo posts. """
caption = models.TextField()
# source_url = models.URLField(blank=True, null=True)
image = ImageField(upload_to=upload_to)
post = models.ForeignKey(PhotoPost, blank=True, null=True, related_name='photos')
user = models.ForeignKey(User, blank=True, null=True, related_name='photos')
class Meta:
app_label = 'posts'
def __unicode__(self):
return 'Photo Object by: ' + str(self.user.get_full_name())
You can use this nice app django-model-utils, using InheritanceManager in your Post class.
A nice example from the docs:
from model_utils.managers import InheritanceManager
class Place(models.Model):
# ...
objects = InheritanceManager()
class Restaurant(Place):
# ...
class Bar(Place):
# ...
nearby_places = Place.objects.filter(location='here').select_subclasses()
for place in nearby_places:
# "place" will automatically be an instance of Place, Restaurant, or Bar
Applying for your situation:
class Post(AutoDateTimeModel):
...
objects = InheritanceManager()
class TextPost(Post):
...
class PhotoPost(Post):
...
And this answers your question: Is there a way to simply query for both types of posts at once?
You can query for posts now, resulting instances of TextPost and Photoposts