Django model subclassing: Get the subclass by querying the superclass - python

The following code is given:
class BaseMedium(models.Model):
title = models.CharField(max_length=40)
slug = models.SlugField()
class A(BaseMedium):
url = models.URLField()
class B(BaseMedium):
email = models.EmailField()
I now want to query every BaseMedium.
b = BaseMedium.objects.all()
How do I print every information including the subclass fields without knowing what the subclass type is?
b[0].a would print the information if b[0] is actually related to an A instance but if it's related to B it would print an DoesNotExist Exception.
This makes sense but I'd like to have a common variable or method that returns the related object.
Maybe my Database layout isn't really great to query that way if so I'd be glad if you'd recommend a better layout.
I thought about using a GenericForeignKey
class Generic(models.Model):
basemedium = models.ForeignKey('BaseMedium')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey('content_type', 'object_id')
but this solution seems to be to complicated and I think you guys have better solutions.

You should check the solution posted by Carl Meyer some time ago. It internally uses the ContentType approach, but it encapsulates it very elegantly .
He also points to an alternative, and more efficient solution, that doesn't need to store an aditional field on the database, but it will only work for direct child classes. If you have several inheritance levels, the first solution is better.

The only way to do this is to explicitly store on the base model what type it is. So have a derived_type (or whatever) field on BaseMedium, and set it on save. Then you can have a get_derived_type method:
def get_derived_type(self):
if self.derived_type == 'A':
return self.a
elif self.derived_type == 'B':
return self.b
and so on.

Thanks mr. Roseman for your reply.
I developed your idea a bit further.
Here is what I came up with:
def related_object(self, default_pointer_name='_ptr'):
models = [A,B] #models
object = None
argument = '%s%s' %(self.__class__.__name__.lower(), default_pointer_name)
query = { argument : self}
for model in models:
try:
object = model.objects.get(**query)
except model.DoesNotExist:
pass
else:
return object
if object == None:
raise RelatedObjectException
return object
This is a method used by BaseMedium.

This worked for me (using self.subclass_name_in_lower_case):
In this example subclasses are TextTreeItem, CategoryTreeItem and KeywordTreeItem.
class TreeItem(MPTTModel):
parent = TreeForeignKey('self', on_delete=models.CASCADE, verbose_name=_('Parent'),
null=True, blank=True, related_name='%(class)s_related')
objects = CustomTreeManager()
#property
def daughter(self):
try:
return self.texttreeitem
except TreeItem.texttreeitem.RelatedObjectDoesNotExist:
pass
try:
return self.categorytreeitem
except TreeItem.categorytreeitem.RelatedObjectDoesNotExist:
pass
try:
return self.keywordtreeitem
except TreeItem.keywordtreeitem.RelatedObjectDoesNotExist:
return self

Related

How to call a superclass method and return the subclass implementation?

I have this models:
from django.db import models
class Item(models.Model):
# ...
def __str__(self):
return 'item'
class SubItemA(Item):
# ...
def __str__(self):
return 'subitem_a'
class SubItemB(Item):
# ...
def __str__(self):
return 'subitem_b'
And I want to call the __str__ method from each Item in Item.object.all() and return each subclass implementation, but it only return the superclass implementation.
Example:
for item in Item.object.all():
print(item.__str__())
On my database I have two SubItemA and two SubItemB. Than it returns:
item
item
item
item
I would like to have some thing like this:
subitem_a
subitem_a
subitem_b
subitem_b
I am lost here.
It's a bit involved. You might look at Django model subclassing: Get the subclass by querying the superclass for a full discussion.
Short answer, from that question:
def related_object(self, default_pointer_name='_ptr'):
models = [A,B] #models
object = None
argument = '%s%s' %(self.__class__.__name__.lower(), default_pointer_name)
query = { argument : self}
for model in models:
try:
object = model.objects.get(**query)
except model.DoesNotExist:
pass
else:
return object
if object == None:
raise RelatedObjectException
return object
# This is a method used by BaseMedium.
With this you will be stumbling upon more problems. A clean solution without reinventing a wheel is to use something like Django Polymorphic

Django - using Foreign key mode attribute

I have two Django models and connected via Foreignkey element and in the second model I need to use the firs model's attribute - example (pseudocode):
Class Category(models.Model):
c_attribute = "Blue"
Class Object(models.Model):
o_category = models.ForeignKey(Category)
o_color = o_category.c_attribute
The key here is the last line - I got error saying that ForeignKey object has no attribute c_attribute.
Thanks
Because o_category is a Key to a Category, not a Category itself!
you can check by type(o_category) to check is not Category!
so you have access to related Cateogry of a Object in other parts of application when connected to database.for example in shell you can write:
c = Category()
c.save()
o = Object(o_category = c, ...) #create Object with desired params
... #some changes to o
o.save()
o.o_category.c_attribute #this will work! :)
You can use to_field='', but that might give you an error as well.
Class Object(models.Model):
o_category = models.ForeignKey(Category)
o_color = models.ForeignKey(Category, to_field="c_attribute")
The best thing is do create a function in your Object model, that would get you the categories c_attribute like so:
def get_c_attribute(self):
return self.o_category.c_attribute

Finding objects without relationship in django

I am learning Django, and want to retrieve all objects that DON'T have a relationship to the current object I am looking at.
The idea is a simple Twitter copycat.
I am trying to figure out how to implement get_non_followers.
from django.db import models
RELATIONSHIP_FOLLOWING = 1
RELATIONSHIP_BLOCKED = 2
RELATIONSHIP_STATUSES = (
(RELATIONSHIP_FOLLOWING, 'Following'),
(RELATIONSHIP_BLOCKED, 'Blocked'),
)
class UserProfile(models.Model):
name = models.CharField(max_length=200)
website = models.CharField(max_length=200)
email = models.EmailField()
relationships = models.ManyToManyField('self', through='Relationship',
symmetrical=False,
related_name='related_to')
def __unicode__ (self):
return self.name
def add_relationship(self, person, status):
relationship, created = Relationship.objects.get_or_create(
from_person=self,
to_person=person,
status=status)
return relationship
def remove_relationship(self, person, status):
Relationship.objects.filter(
from_person=self,
to_person=person,
status=status).delete()
return
def get_relationships(self, status):
return self.relationships.filter(
to_people__status=status,
to_people__from_person=self)
def get_related_to(self, status):
return self.related_to.filter(
from_people__status=status,
from_people__to_person=self)
def get_following(self):
return self.get_relationships(RELATIONSHIP_FOLLOWING)
def get_followers(self):
return self.get_related_to(RELATIONSHIP_FOLLOWING)
def get_non_followers(self):
# How to do this?
return
class Relationship(models.Model):
from_person = models.ForeignKey(UserProfile, related_name='from_people')
to_person = models.ForeignKey(UserProfile, related_name='to_people')
status = models.IntegerField(choices=RELATIONSHIP_STATUSES)
This isn't particularly glamorous, but it gives correct results (just tested):
def get_non_followers(self):
UserProfile.objects.exclude(to_people=self,
to_people__status=RELATIONSHIP_FOLLOWING).exclude(id=self.id)
In short, use exclude() to filter out all UserProfiles following the current user, which will leave the user themselves (who probably shouldn't be included) and all users not following them.
i'v been searching for a method or some way to do that for like an hour, but i found nothing.
but there is a way to do that.
you can simply use a for loop to iterate through all objects and just remove all objects that they have a special attribute value.
there is a sample code here:
all_objects = className.objects.all()
for obj in all_objects:
if obj.some_attribute == "some_value":
all_objects.remove(obj)
Solution to the implementation of get_non_followers:
def get_non_following(self):
return UserProfile.objects.exclude(to_person__from_person=self, to_person__status=RELATIONSHIP_FOLLOWING).exclude(id=self.id)
This answer was posted as an edit to the question Finding objects without relationship in django by the OP Avi Meir under CC BY-SA 3.0.
current_userprofile = current_user.get_profile()
rest_of_users = Set(UserProfile.objects.filter(user != current_userprofile))
follow_relationships = current_userprofile.relationships.filter(from_person=current_user)
followers = Set();
for follow in follow_relationships:
followers.add(follow.to_person)
non_followeres = rest_of_users.difference(followers)
Here non_followers is the list of userprofiles you desire. current_user is the user whose non_followers you are trying to find.
I haven't tested this out, but it think it should do what you want.
def get_non_followers(self):
return self.related_to.exclude(
from_people__to_person=self)

Django/Python: how can i filter with Model's custom method?? Is this possible?

I have a model with a custom method. Here's the example:
class MyModel(models)
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50)
nickname = models.CharField(max_length=50)
time_created = models.DateTimeField(auto_now_add=True)
def sample_method(self)
right_now = datetime.now()
created_time = self.time_created
time2compare = timedelta(minutes=3)
timeout = True
if(right_now - created_time) > time2compare:
timeout = False
return timeout
What I'm trying to do is do something like this (i know this doesn't work)
queryset = MyModel.objects.filter(timeout=False)
How could i solve this problem? Thanks!
You cannot use a custom method on your model for filtering, because the filters will be resolved to saw sql, which will not be possible with some python code. Nonetheless the following should solve your problem:
import datetime
queryset = MyModel.objects.filter(\
time_created__lt=(datetime.now()-datetime.timedelta(minutes=3)))
Your timeout test is very trivial to reimplement as a queryset filter.
For a really complex test that can't be expressed in Django query language or raw SQL, you can write something like:
def sample_filter(self, method_name, arg=False)
for i in self.objects.all():
if getattr(self, method_name)() == arg:
yield i
This method will return an iterator to every instance x of the Model where x.method_name() == arg. However, this can't be chained like a queryset.

How does django one-to-one relationships map the name to the child object?

Apart from one example in the docs, I can't find any documentation on how exactly django chooses the name with which one can access the child object from the parent object. In their example, they do the following:
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
def __unicode__(self):
return u"%s the restaurant" % self.place.name
# Create a couple of Places.
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()
# Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
# A Restaurant can access its place.
>>> r.place
<Place: Demon Dogs the place>
# A Place can access its restaurant, if available.
>>> p1.restaurant
So in their example, they simply call p1.restaurant without explicitly defining that name. Django assumes the name starts with lowercase. What happens if the object name has more than one word, like FancyRestaurant?
Side note: I'm trying to extend the User object in this way. Might that be the problem?
If you define a custom related_name then it will use that, otherwise it will lowercase the entire model name (in your example .fancyrestaurant). See the else block in django.db.models.related code:
def get_accessor_name(self):
# This method encapsulates the logic that decides what name to give an
# accessor descriptor that retrieves related many-to-one or
# many-to-many objects. It uses the lower-cased object_name + "_set",
# but this can be overridden with the "related_name" option.
if self.field.rel.multiple:
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
return None
return self.field.rel.related_name or (self.opts.object_name.lower() + '_set')
else:
return self.field.rel.related_name or (self.opts.object_name.lower())
And here's how the OneToOneField calls it:
class OneToOneField(ForeignKey):
... snip ...
def contribute_to_related_class(self, cls, related):
setattr(cls, related.get_accessor_name(),
SingleRelatedObjectDescriptor(related))
The opts.object_name (referenced in the django.db.models.related.get_accessor_name) defaults to cls.__name__.
As for
Side note: I'm trying to extend the
User object in this way. Might that be
the problem?
No it won't, the User model is just a regular django model. Just watch out for related_name collisions.

Categories

Resources