How do I override related_name in inherited/child objects? - python

Given the following models:
class Module(models.Model):
pass
class Content(models.Model):
module = models.ForeignKey(Module, related_name='contents')
class Blog(Module):
pass
class Post(Content):
pass
I would like to be able to get all the "post" objects owned by blog doing something like:
b = Blog.objects.get(pk=1)
b.posts.all()
However, I haven't figured out a good way of doing this. I can't use b.contents.all() as I need Post instances and not Content instances. I won't ever have a root content object, every content object is going to be subclassed, but I can't use abstract classes as I want a central table with all my content in it and then there will be content_blog etc tables for all the unique inherited pieces of content.
I also tried doing this
class Content(models.Model):
module = models.ForeignKey(Module, related_name='%(class)')
but that failed miserably as far as I could tell.

The simplest way might add a method to Blog model to return a Post queryset, like this:
class Blog(Module):
def _get_posts(self):
return Post.objects.filter(module=self)
posts = property(_get_posts)
The problem is you have to add method for every sub-model. The related_name seems only works for abstract base class.

This solution comes to my mind:
# ...
class Blog(Module):
#property
def posts(self):
return self.contents
class Post(Content):
pass
This way, doing blog.posts is the same as doing blog.contents:
>>> blog = Blog.objects.get(pk=1)
>>> blog.posts.all()
# [ ... ]

Related

Get all items with all properties using django multi-table inheritance and django rest framework

I'm using Django multi-table inheritance:
class Parent():
common_property = ...
class Child1(Parent):
child1_specific_property = ...
class Child2(Parent):
child2_specific_property = ...
And want to expose the list of all items on the same endpoint.
If I make a basic serializer and view for the Parent model, I would just get the common properties (the ones living on that model), but in this case I want to get all child-specific properties for every item. Ideally something like this:
items {
type_1: {
common_property
child1_specific_property
}
type_2: {
common_property
child2_specific_property
}
}
Am I missing any trivial way to do this?
I ended up finding a well performing way to manually do this. A simpler option would be to use libraries like django-polymorphic, as #dirkgroten commented.
Models are defined using multi-table inheritance, as stated in my question:
class Parent():
common_property = ...
class Child1(Parent):
child1_specific_property = ...
class Child2(Parent):
child2_specific_property = ...
On the serializer, we overwrite the to_representation method in order to map every instance to the correct child serializer:
from rest_framework import serializers
class Parent(serializers.BaseSerializer):
def to_representation(self, instance):
try:
return Child_1_Serializer(instance=instance.child1).data
except Child1.DoesNotExist:
pass
try:
return Child_2_Serializer(instance=instance.child2).data
except Child2.DoesNotExist:
pass
return super().to_representation(instance)
On the view we use select_related when defining the queryset, to avoid performing one query for every child when getting the list. More info about select_related can be found on the Queryset API reference.
class ParentViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Parent.objects.all().select_related('child1').select_related('child2')
serializer_class = ParentSerializer
Filter and other stuff can be added to the serializer as you would do with simple models.

django - Creating custom queryset for RelatedManager

I tried about 25 Stackoverflow links and nobody seems to have a working solution for this problem.
I created a custom queryset like this:
class ProjectQuerySet(models.QuerySet):
def get_active(self):
from apps.projectmanagement.models import Project
return self.filter(active=True)
class ProjectManager(models.Manager):
def get_queryset(self):
return ProjectQuerySet(self.model, using=self._db)
This works great if I start at the model like
Project.objects.get_active()
But if I want to use it in a relation, no luck so far:
employee.projects.get_active()
I always get this error:
AttributeError: 'ManyRelatedManager' object has no attribute 'get_active'
What I've tried so far:
I read that use_for_related_fields = True in the manager class is deprecated. Does not work anyway on django v2.1
Adding this in my model, as half the internet states:
class Project(models.Model):
...
objects = ProjectManager()
class Meta:
base_manager_name = 'objects'
Trying to avoid the RelatedManager and to work with a queryset:
employee.projects.all().get_active()
Any ideas what I've been doing wrong? And how would I solve this? Can't be too hard, right?
As the docs state, "Base managers aren’t used when querying on related models". The example they give is for going in the other direction, ie project.employee.
There is no way to do this using managers themselves. The best bet is to define a method on your model:
class Employee(models.Model):
...
def get_active_projects(self):
return self.projects.filter(active=True)
# or
return Project.objects.filter(employee=self).get_active()

Calling Django child model method from parent requires InheritanceManager?

I have a Post class for my Django app that has several subclasses TextPost, AudioPost, etc, each with their own render_html() method.
class Post(models.Model):
author = models.ForeinKey(User,...)
title = models.CharField(...)
pub_date = models.DateTimeField(...)
...
def render_html(self):
return "rendered title, author date"
class AudioPost(Post):
audioFile = FileField(...)
def render_html(self):
return "Correct audio html"
...
each of these child models has an ModelForm with rules for uploading, validation, and saving.
In a home page view, I'd like to take all posts, arrange them by date, and render them. To me this should be as simple as
## in view
context = { 'posts' : Post.objects.order_by('-pub_date')[:5] }
and
## in template
{{ post.render_html() | safe }}
I remember things working this way for abstract classes in Java. But when I do it this way in Python, the render_html method gets called as if they are each members of the parent class. I've looked up how Django does multi-table inheritence, it seems like I either need to check the generated OneToOneFields one by one until I've found one that doesn't raise an exception, or use the InheritanceManager utility manager. Is one of those two ways the best way to do this or should I do something else?
I solved this via the following method inside Post, which allows me to do
{{ post.get_subclass().render_html() }}
inside the template. Assuming 4 subclasses, AudioPost, VideoPost, TextPost, and RichTextPost:
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
class Post(models.Model):
...
...
def get_subclass(self):
try:
textpost = self.textpost
return textpost
except ObjectDoesNotExist:
pass
try:
audiopost = self.audiopost
return audiopost
except ObjectDoesNotExist:
pass
try:
videopost = self.videopost
return videopost
except ObjectDoesNotExist:
pass
try:
richtextpost = self.richtextpost
return richtextpost
except ObjectDoesNotExist:
pass
raise ObjectDoesNotExist
I would suggest another method for your problem which you can use to get all the subclasses of the base class. It will be a bit of handy as you don't need to get query set for every child class manually
querysets_child = [child_cls.objects.all() for child_cls in vars()['BaseClass'].__subclasses__()]
The method you refer to which works on java but I don't think it can work here. Either you use child classes manually or get all child classes with subclass function mentioned above.

Attaching extra information to model instance - django

I have a django model that I want to attach an extra piece of information to, depending on the environment the instance is in (which user is logged in). For this reason, I don't want to do it at the database level.
Is this okay to do? Or are there problems that I don't foresee?
in models.py
class FooOrBar(models.Model):
"""Type is 'foo' or 'bar'
"""
def __init__(self, type):
self.type = type
in views.py
class FooCheck(FooOrBar):
"""Never saved to the database
"""
def __init__(self, foo_or_bar):
self.__dict__ = foo_or_bar.__dict__.copy()
def check_type(self, external_type):
if external_type == 'foo':
self.is_foo = True
else:
self.is_foo = False
foos_or_bars = FooOrBar.objects.all()
foochecks = map(FooCheck, foos_or_bars)
for foocheck in foochecks:
foocheck.check_type('foo')
extra credit question: Is there a more efficient way of calling a method on multiple objects i.e. replacing the last forloop with something clever?
Okay, this does not work. Trying to delete a FooOrBar objects throws a complaint about
OperationalError at /
no such table: test_FooCheck
To get around this I'm just not going to inherit from FooOrBar, but if anyone has a suggestion on a better way to do it I'd be interested in hearing it
I had a similar issue, I did something like:
class Foo(models.Model):
# specific info goes here
class Bar(models.Model):
# specific info goes here
class FooBar(models.Model):
CLASS_TYPES = {
"foo":Foo,
"bar":Bar
}
type = models.CharField(choices=CLASS_TYPES)
id = models.IntegerField()
#field to identify FooBar
then you can get the object back using
object = FooBar.CLASS_TYPES[instance.type].objects.get(id=instance.id)
where instance is the FooBar instance

Django - Checking the type of Multi-table inheritence Querysets

I'm trying to hold a kind of table of contents structure in my database. Simplified example:
models.py
class Section (models.Model):
title = models.CharField(max_length=80)
order = models.IntegerField()
class SectionClickable(Section):
link = models.CharField(max_length=80)
class SectionHeading(Section):
background_color = models.CharField(max_length=6)
views.py
sections = Section.objects.filter(title="Hello!")
for section in sections:
if(section.sectionheading):
logger.debug("It's a heading")
I need to do some processing operations if it's a SectionHeading instance, but (as in the Django manual), accessing section.sectionheading will throw a DoesNotExist error if the object is not of type SectionHeading.
I've been looking into alternatives to this kind of problem, and I'm skimming over Generic Foreign Keys in the contenttypes package. However, this seems like it would cause even more headaches at the Django Admin side of things. Could anyone advise on a better solution than the one above?
Edit: I avoided abstract inheritence because of the order field. I would have to join the two QuerySets together and sort them by order
well you could check the type:
if isinstance(section, SectionHeading)
but duck typing is generally preferred
edit:
actually, that probably won't work. the object will be a Section. but you can look for the attribute:
if hasattr(section, 'sectionheading')
or
try:
do_something_with(section.sectionheading)
except AttributeError:
pass # i guess it wasn't one of those
The solution I came up using involved an extra field pointing to the (rather useful) ContentType class:
class Section(models.Model):
name = models.CharField(max_length=50)
content_type = models.ForeignKey(ContentType,editable=False,null=True)
def __unicode__(self):
try:
return self.as_leaf_class().__unicode__()
except:
return self.name
def save(self, *args, **kwargs):
if(not self.content_type):
self.content_type = ContentType.objects.get_for_model(self.__class__)
super(Section, self).save(*args, **kwargs)
def as_leaf_class(self):
content_type = self.content_type
model = content_type.model_class()
if(model == Section):
return self
return model.objects.get(id=self.id)
If you're going through "base" object, I think this solution is pretty nice and comfortable to work with.
I've been using something similar to what second suggests in his edit:
class SomeBaseModel(models.Model):
reverse_name_cache = models.CharField(_('relation cache'), max_length=10,
null=True, editable=False)
def get_reverse_instance(self):
try:
return getattr(self, self.reverse_name_cache)
except AttributeError:
for name in ['sectionclickable', 'sectionheading']:
try:
i = getattr(self, name)
self.reverse_name_cache = name
return i
except ObjectDoesNotExist:
pass
Now, this isn't exactly pretty, but it returns the subclass instance from a central place so I don't need to wrap other statements with try. Perhaps the hardcoding of subclass reverse manager names could be avoided but this approach was enough for my needs.
OP here.
While second's answer is correct for the question, I wanted to add that I believe multi-table inheritence is an inefficient approach for this scenario. Accessing the attribute of the sub-class model would cause a query to occur - thus requiring a query for every row returned. Ouch. As far as I can tell, select_related doesn't work for multi-table inheritence yet.
I also ruled out ContentTypes because it wouldn't do it elegantly enough and seemed to require a lot of queries also.
I settled on using an abstract class:
class Section (models.Model):
title = models.CharField(max_length=80)
order = models.IntegerField()
class Meta:
abstract=True
ordering=['order']
Queried both tables:
section_clickables = SectionClickable.objects.filter(video=video)
section_headings= SectionHeading.objects.filter(video=video)
and joined the two querysets together
#Join querysets http://stackoverflow.com/questions/431628/how-to-combine-2-or-more-querysets-in-a-django-view
s = sorted(chain(section_headings, section_clickables), key=attrgetter('order'))
Lastly I made a template tag to check the instance:
from my.models import SectionHeading, SectionClickable
#register.filter()
def is_instance(obj, c):
try:
return isinstance(obj, eval(c))
except:
raise ObjectDoesNotExist('Class supplied to is_instance could not be found. Import it in the template tag file.')
so that in my template (HamlPy) I could do this:
- if s|is_instance:"SectionClickable"
%span {{s.title}}
- if s|is_instance:"SectionHeading"
%span{'style':'color: #{{s.color}};'}
{{s.title}}
The result is that I only used two queries, one to get the SectionClickable objects and one for the SectionHeading objects

Categories