Publish date, managers and relations - python

Consider the following models:
class Artist(GalleryMixin, models.Model):
name = models.CharField(max_length=200)
class Exhibition(GalleryMixin, models.Model):
name = models.CharField(max_length=200, verbose_name=_(u'title'))
artists = models.ManyToManyField('Artist')
Now, I added a publish_on DateTimeField and I don't want to see any objects
on the website with publish_date superior to datetime.datetime.now(), so I
made a manager (using documentation) and it looks like this:
class PublicObjectManager(models.Manager):
use_for_related_fields = True
def get_query_set(self):
qs = super(PublicObjectManager, self).get_query_set()
qs = qs.filter(publish_on__lte=datetime.datetime.now())
return qs
class Artist(GalleryMixin, models.Model):
name = models.CharField(max_length=200)
publish_on = models.DateTimeField(null=True, blank=True)
public_objects = PublicObjectManager()
objects = models.Manager()
class Exhibition(GalleryMixin, models.Model):
name = models.CharField(max_length=200, verbose_name=_(u'title'))
artists = models.ManyToManyField('Artist')
publish_on = models.DateTimeField(null=True, blank=True)
public_objects = PublicObjectManager()
objects = models.Manager()
This works great: non-published objects never appear on the website... But admins are not be able to see non-published objects
! So I could override the admin queryset of course, but it would be hard to
override relations querysets which means that admins could not use Select
fields to create relations to non-published objects.
Ie. admin would not be able to select a non-published Artist in Exhibition.artists.
What's your take on this ?

I've done this by just writing a method on the base object rather than using managers (to me it's more readable than having to remember about 'hidden' base manager changes):
class Exhibition(GalleryMixin, models.Model):
...
def publishedArtists(self):
return self.artist_set.filter(publish_on__lte=datetime.datetime.now())
Then in your template you could just do:
{{exhibition.publishedArtists}}

Related

Unable to get related data from ManyToManyField

I'm trying to fetch related objects from below two models.
Following django models with ManyToManyField relationship.
Book
class Book(models.Model):
authors = models.ManyToManyField(
to=Author, verbose_name="Authors", related_name="books_author"
)
bookshelves = models.ManyToManyField(
to=Bookshelf, verbose_name="Bookshelf", related_name="books_shelves"
)
copyright = models.NullBooleanField()
download_count = models.PositiveIntegerField(blank=True, null=True)
book_id = models.PositiveIntegerField(unique=True, null=True)
languages = models.ManyToManyField(
to=Language, verbose_name=_("Languages"), related_name="books_languages"
)
Author
class Author(models.Model):
birth_year = models.SmallIntegerField(blank=True, null=True)
death_year = models.SmallIntegerField(blank=True, null=True)
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Meta:
verbose_name = _("Author")
verbose_name_plural = _("Author")
I have to fetch all the Auhtors with their related books. I have tried a lot of different ways none is working for me.
First way : using prefetch_related
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.exclude(name__isnull=True)
def get_queryset(self):
auths = queryset.prefetch_related(Prefetch("books_author"))
Second way using related_name 'books_auhtor'
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.exclude(name__isnull=True)
def get_queryset(self):
auths = queryset.books_author.all()
None of the above ways worked for me. I want to prepare a list of Authors and their associated books.
For ex:-
[{'Author1':['Book1','Book2'],... }]
Prefetching is not necessary, but can be used to boost efficiency, you can work with:
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorWithBooksSerializer
queryset = Author.objects.exclude(name=None).prefetch_related('books_author')
In the AuthorWithBooksSerializer, you can then add the data of the books, for example:
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('book_id', 'copyright')
class AuthorWithBooksSerializer(serializers.ModelSerializer):
books = BookSerializer(source='books_author', many=True)
class Meta:
model = Author
fields = ('name', 'books')
Here the books will use the BookSerializer and thus encode a list of dictionaries.
While you can use the name of the author as object key, I strongly advise against this: it makes the object less accessible since the keys are no longer fixed and if these contain spaces, it can also result in more trouble obtaining the value(s) associated with a given attribute name.

Django - manager on ManyToMany relationship

I have 3 models
class Person(models.Model):
name = models.CharField(max_length=128)
class Company(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField (Person, through = 'Membership', related_name = 'companies')
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
is_admin = models.BooleanField()
I can then call person.companies.all() to get the list of companies associated with person.
How do I create a manager to have the list of companies associated with person, but whose person is admin (is_admin = True)?
You can create a manager like the following:
managers.py:
from django.db import models
class AdminCompaniesManager(models.Manager):
def get_queryset(self):
return super().get_queryset().companies.filter(membership__is_admin=True)
and then in your Person model (please remind the objects manager):
class Person(models.Model):
name = models.CharField(max_length=128)
objects = models.Manager()
administered_companies = AdminCompaniesManager()
Now you can easily call the following (e.g. in your views):
my_person.administered_companies.all()
PS: a very efficient option (e.g. if you are in a view and you need the list of company ids by a given person) is to query the membership model directly, so you can optimize the query for data retrieval from DB avoiding the joins:
Membership.objects.filter(is_admin=True, person=person).values_list('company_id')
You can filter with:
person.companies.filter(membership__is_admin=True)
This will filter the junction table Membership, such that it will only retrieve Companys for which the Membership has is_admin set to True.
Another option is to retrieve this with:
Company.objects.filter(membership__is_admin=True, members=person)
You can attach this to the Person model with:
class Person(models.Model):
name = models.CharField(max_length=128)
#property
def admin_companies(self):
return self.companies.filter(membership__is_admin=True)

Arbitrarily set position/order of ManyToMany field in django admin?

I have the below models:
# Child
class Media (models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=128,unique=True)
file = models.FileField()
enabled = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
# Parent
class Gallery(models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=128,unique=True)
description = models.TextField(max_length=254,null=True)
medias = models.ManyToManyField(Media,related_name='medias')
enabled = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "gallery"
verbose_name_plural = "galleries"
def __str__(self):
return self.name
I would like to be able to sort the child table by setting it in junction table; therefore not affecting the child table. I'm thinking of setting position field in junction table, is manually adding it in DB the only way of doing that? I'm fairly new to Django and I'm sorry in advance if this happens to be just a basic question.
Usual ManyToMany doesn't work here, because association table should contain order. So, this case you have to bring through to the mix:
class Gallery(models.Model):
medias = models.ManyToManyField('Media',related_name='medias', through='ChildGallery')
Where ChildGallery is a join model:
class ChildGallery(models.Model):
child = models.ForeignKey(Child)
gallery = models.ForeignKey(Gallery)
order = models.IntegerField()
class Meta:
ordering = ['order']
I don't know how you would like to implement order, but if it's just a number, you could use IntegerField as I showed above.
P.S. Note I wrapped all association models in quotes (like 'Child' not Child). It allows to break circular dependencies, so I recommend to use this way

Django M2MFields 'through' widgets

Are there exists any examples of Django widgets which can be useful for ManyToManyFields with 'through' attributes? For example, i have these models (got the source from django documentation):
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Obvisously, standart ModelMultipleChoiceField won't work here. I need to populate 'date_joined' , and 'invite_reason' while adding. Which is the simpliest way to achieve this?
This is a bit too complex for a simple widget. I can't even imagine how it would look like. You will have to use inline formsets for that purpose.
This should give something like this:
from django.forms import inlineformset_factory
MembershipFormSet = inlineformset_factory(Group, Membership, fields=(
'person', 'date_joined', 'invite_reason'))
group = Group.objects.get(pk=group_id)
formset = MembershipFormSet(instance=group)
Within django.contrib.admin, you can use inlines with InlineModelAdmin.

Django Inheriting from classes

I have run into a problem developing my Django site.
from django.db import models
class TitlePost(models.Model):
title_name = models.CharField(max_length=100, unique=True)
title_body = models.TextField(max_length=30000)
title_why = models.TextField(max_length=250, null=True)
title_publication_date = models.DateTimeField('date')
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
def __unicode__(self):
return self.title_name
class TopTitlesPostPage(models.Model):
title_post = models.OneToOneField(TitlePost)
hello = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.hello
class NewTitlesPostPage(models.Model):
title_post = models.OneToOneField(TitlePost)
hello = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.hello
Why don't TopTitlesPostPage and NewTitlesPostPage inherit all the attributes from TitlePost? For instance, if I try to call the likes in my template using TopTitlesPostPage, it will not execute because the likes attribute is not inherited. Does OneToOneField have something to do with the problem? I did read that making TitlePost a meta class will help but I need it to have a table in my database. I actually want all of them to have a table in my data base. Then again, maybe I am approaching this the wrong way and I should use just TitlePost as a model to generate everything?
The behaviour you would like to see is called multi table inheritance. Every child class internally ends up with the same thing that you wrote, so with a one to one field to the base class TitlePost, but it's internally managed by django.
If you do multiple inheritance like the code below you will be able to write:
k=TopTitlesPostPage.objects.create(hello="Hello",title_name="Heh")
That means the fields will be directly accessible.
from django.db import models
class TitlePost(models.Model):
title_name = models.CharField(max_length=100, unique=True)
title_body = models.TextField(max_length=30000)
title_why = models.TextField(max_length=250, null=True)
title_publication_date = models.DateTimeField('date')
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
def __unicode__(self):
return self.title_name
class TopTitlesPostPage(TitlePost):
hello = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.hello
class NewTitlesPostPage(TitlePost):
hello = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.hello
In case you are never actually going to reference the base class TitlePost, but only its children it might be more appropriate to make `TitlePost abstract:
class TitlePost(models.Model):
title_name = models.CharField(max_length=100, unique=True)
title_body = models.TextField(max_length=30000)
title_why = models.TextField(max_length=250, null=True)
title_publication_date = models.DateTimeField('date')
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
class Meta:
abstract = True
def __unicode__(self):
return self.title_name
Making TitlePostabstract will omit the creation of the table TitlePostin the database, and the child models will end up with the fields of the base class inserted into their own tables seperately. If the base class is just for factoring out common functionality this is the preferred way to go.
For huge queries this will also make a difference in performance because the ORM will need to do less JOINoperations.
It's not possible to install Foreign Keys to abstract models in Django.
You can however install Foreign Keys to a non abstract base class. The only limitation is that the reverse Foreign Key relation will return the base class instances.
You can circumvent this limitation by using django-polymorphic.
Django Polymorphic allows you to query the base class objects but retrieves the child class instances:
>>> Project.objects.create(topic="Department Party")
>>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner")
>>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
>>> Project.objects.all()
[ <Project: id 1, topic "Department Party">,
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
To use django polymorphic you only need to declare your models with Polymorphic Model as base class:
from django.db import models
from polymorphic import PolymorphicModel
class ModelA(PolymorphicModel):
field1 = models.CharField(max_length=10)
class ModelB(ModelA):
field2 = models.CharField(max_length=10)
class ModelC(ModelB):
field3 = models.CharField(max_length=10)
Foreign keys will also return the child class instances, which is really cool if you're trying to be polymorphic.
# The model holding the relation may be any kind of model, polymorphic or not
class RelatingModel(models.Model):
many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model
>>> o=RelatingModel.objects.create()
>>> o.many2many.add(ModelA.objects.get(id=1))
>>> o.many2many.add(ModelB.objects.get(id=2))
>>> o.many2many.add(ModelC.objects.get(id=3))
>>> o.many2many.all()
[ <ModelA: id 1, field1 (CharField)>,
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
Take into account that these queries will be slightly less performant.
U need to extend the classes like follows:
class TopTitlesPostPage(TitlePost):
U can add more and inherit from multiple models just by mentionin g all the models comma separated! This all the fields from the models will be created in the child class as well
EDIT:
The way i would do it is to create an Abstract class which contains all your common fields and extend it into your TitlePost, TopTitlesPostPagea and NewTitlesPostPage
You need to have TopTitlesPostPage and NewTitlesPostPage extend the base class of TitlePost like so ...
class TopTitlesPostPage(models.Model)
You don't need a OneToOneField if you are inheriting from the base class, since the attributes of TitlePost will be available to you in the subclass. If you want to make TitlePost abstract (you can not declare an instance of that class, only inherit from it) you have to add it to the meta class
class TitlePost(models.Model):
class Meta:
abstract = True
Here is a link to the documentation.

Categories

Resources