Calling Django child model method from parent requires InheritanceManager? - python

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.

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.

In Django how to avoid boilerplate code for getting model instance by pk in a view

Here is example code:
def someview(request):
try:
instance = SomeModel.objects.get(id=request.GET.get('id'))
except SomeModel.DoesNotExist:
instance = None
except ValueError:
# This error may occur if user manually enter invalid (non-integer)
# id value (intentionally or not) in a browser address bar, e.g.
# http://example.com/?id=2_foo instead of http://example.com/?id=2
# This raises ValueError: invalid literal for int() with base 10: '2_'
instance = None
...
Is there a best practice to get a model instance by pk without writing this boilerplate code over and over? Should I use some predefined shortcut in Django or just roll my own?
I was sure that I should use Django's DetailView or SingleObjectMixin but curiously enough it doesn't handle the ValueError exception from my example https://github.com/django/django/blob/master/django/views/generic/detail.py#L50
Is it implied that I have to specify correct integer regexp for pk kwarg in urlconf? Ok, likely. But what if I get pk from request querystring?
UPD I have special logic to do with instance either it's None or not.
You can also use Django's built in shorcut get_object_or_404() that it's designed for this specifically. That function will raise an Http404 exception in case the object doesn't exist. If you want to get None instead of raising the exception, you can create a helper function to accomplish it very easily:
def get_object_or_none(klass, *args, **kwargs):
try:
return get_object_or_404(klass, *args, **kwargs)
except Http404:
return None
Hope this helps!
The first part of your try/except block can be simplified by using django-annoying:
from annoying.functions import get_object_or_None
instance = get_object_or_None(SomeModel, id=request.GET.get('id'))
FYI, you can also just extract get_object_or_None from the package (see source).
There are many generic class based views that might be helpful, in your case DetailView could work.
from django.views.generic.detail import DetailView
class SomeModelDetailView(DetailView):
model = SomeModel
You can overwrite get_object method to change default behaviour.
def get_object(self, queryset=None):
return SomeModel.objects.get(pk=self.get_pk())
And lastly if object is none you should probably display custom 404 page.

Returning extended fields in JSON

I have two tabels(Ingredient_Step and Ingredient) in on relation as you can see below:
Models.Py
class Ingredient_Step(models.Model):
ingredient = models.ForeignKey(Ingredient)
Step = models.ForeignKey(Step)
def __unicode__(self):
return u'{}'.format(self.Step)
class Ingredient(models.Model):
IngredientName = models.CharField(max_length=200,unique=True)
Picture = models.ImageField(upload_to='Ingredient')
def __unicode__(self):
return u'{}'.format(self.IngredientName)
In a function, i need serialize a JSON object from a query that returns from "Ingredient_step", but I need send the field "IngredientName", who comes from "Ingredient" table.
I try using "ingredient__IngredientName" but it fails.
Views.Py:
def IngredientByStep(request):
if request.is_ajax() and request.GET and 'id_Step' in request.GET:
if request.GET["id_Step"] != '':
IngStp = Ingredient_Step.objects.filter(Step =request.GET["id_Step"])
return JSONResponse(serializers.serialize('json', IngStp, fields=('pk','ingredient__IngredientName')))
How i can call extends field from a relation?
Thanks
This "feature" of Django (and many ORM's like SQLAlchemy) are called Lazy Loading, meaning data is only loaded from related models if you specifically ask for them. In this case, build your IngStp as a list of results, and make sure to access the property for each result before serializing.
Here's an example of how to do that: Django: Include related models in JSON string?

How do I override related_name in inherited/child objects?

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()
# [ ... ]

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