Generic object "ownership" in Django - python

Suppose I have the following models:
class User(models.Model):
pass
class A(models.Model):
user = models.ForeignKey(User)
class B(models.Model):
a = models.ForeignKey(A)
That is, each user owns some objects of type A, and also some of type B. Now, I'm writing a generic interface that will allow the user to view any objects that it owns. In a view, of course I can't say something like "objects = model.objects.filter(user=user)", since B has no attribute 'user'. What's the best approach to take here?

The way I would do it is to simply go through the object 'a' on class B. So in the view, I would do:
objects = B.objects.get(user=a.user)
objects += A.objects.get(user=user)
The reason I would do it this way is because these are essentially two database queries, one to retrieve a bunch of object A's and one to retrieve a bunch of object B's. I'm not certain it's possible in Django to retrieve a list of both, simply because of the way database inheritance works.
You could use model inheritance as well. This would be making a base class for both objects A and B that contains the common fields and then retrieving a list of the base classes, then convert to their proper types.
Edit: In response to your comment, I suggest then making a base class that contains this line:
user = models.ForeignKey(User)
Class A and B can then inherit from that base class, and you can thus now just get all of the objects from that class. Say your base class was called 'C':
objects = C.objects.get(user=user)
That will obtain all of the C's, and you can then figure out their specific types by going through each object in objects and determining their type:
for object in objects:
if object.A:
#code
if object.B:
#code

Related

Share data between base class and Meta class

I have the following django model:
class Article(models.Model):
filename = models.CharField(max_length=255)
collection = models.ForeignKey(Collection, on_delete=models.CASCADE)
keys = ['filename', 'collection']
class Meta:
constraints = [
models.UniqueConstraint(
fields=['filename', 'collection'],
name='article_key')
]
As you can see I've defined the same list ['filename', 'collection'] in both the base class and the Meta class. I would like to define it once. I can't define it in Meta because then I get 'Meta got an unrecognised attribute 'keys'. So I must define it in the base class and access it from Meta. I don't know how to share data between the two. I've tried doing:
self.keys
in Meta but that gives 'self is not defined'. I've also tried with just 'keys' but that's also not defined. Any tips? Thanks.
EDIT
Thank you to Willem for pointing out that I can define keys in Meta if I just call it '_keys'. If I do this, however, the question is then how do I access _keys from the base class? I've tried 'meta._keys' and 'Meta._keys'. Both not defined.
EDIT 2
For clarity, the reason that I want 'keys' defined in the base class is that I will (a) be accessing it from properties on the base class, and (b) want to be able to access it from the outside.
You can declare it before the class, then reference it from both the model class and it' Meta:
# making it a tuple since you probably don't want
# it to be mutable
_ARTICLE_KEYS = ('filename', 'collection')
class Article(models.Model):
# making it an implementation attribute since you
# probably don't want to be writeable
# (hint: provide a read-only property for access)
_keys = _ARTICLE_KEYS
class Meta:
constraints = [
models.UniqueConstraint(
fields=_ARTICLE_KEYS,
name='article_key')
]
But this is still ugly IMHO and very probably unecessary - the model's methods should be able to access those values thru self._meta.contraints[0].fields or something similar (don't have models with such constraints at hand right now so I can check how this is actually transformed by the models's metaclass but inspecting self._meta in your django shell should give you the answer).
The methods of a nested class cannot directly access the instance attributes of the outer class.
So, in your case, If you won't use the keys list in the Article class, just defined it once in the Meta class. Otherwise, you need to defined twice!

Copy all fields of a class derived from an abstract model (Django)

I have two models A and B of the same structure derived from the same abstract model:
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
# ... # more fields
class Meta:
abstract = True
class A(CommonInfo):
pass
class B(CommonInfo):
pass
Now having an object of class A, I want to create an object of class B with the same values of fields.
What is the proper Django way to copy all fields of one object to the other?
The only way I know is to enumerate all fields (by the way, how to do it?) of an object and store them in the other object. But is there an easier way?
You could use model_to_dict(..) and use this dictionary in the construction of a B object, like:
from django.forms.models import model_to_dict
my_b = B(**model_to_dict(
my_a,
fields=[f.name for f in CommonInfo._meta.fields],
))
# some processing
my_b.save()
Note that if the CommonInfo contains foreign keys to objects, then these references will be copied, but no new referred objects will be constructed. Furthermore this will not work for many-to-many fields, so you need to exclude these (and add the related objects later).

is it possible to make a query lookup for given model inheritance?

Below are my models and I am using InheritanceManager of django-model-utils. i decided the field user to be in the subclasses because i need to access the Child subclass via the relation like user.child1_set.all().
class Mother(models.Model):
objects = InheritanceManager()
class Child1(Mother):
user = models.ForiegnKey(User)
class Child2(Mother):
user = models.ForiegnKey(User)
the problem is, when i want to query starting from Mother class, there seems to be no query that makes it.
i've tried these codes.
Mother.objects.filter(user=SOMEUSER)
Mother.objects.filter(child1__user=SOMEUSER)
Mother.objects.select_subclasses().filder(user=SOMEUSER)
Mother.objects.select_subclasses().filder(child1__user=SOMEUSER)
any advice would be appreciated.

foreignkey relationships

Lets say i have 3 classes, A, B, C.
class A(models.Model):
comment = models.CharField(max_length=600, default="None")
rating = models.IntegerField(default=1, choices=CHOICES, name='rating')
date = models.CharField(max_length=50, default='nonee')
class B(models.Model):
Aname = models.ForeignKey('A', related_name='AB')
classC = models.ForeignKey('C', related_name='BC')
class C(models.Model)
#some info
def average_rating(self):
return self.?????.all().aggregate(Avg('rating')).values()[0]
How is it that I go from a view where my self is an object, all the way back to Class A so that I can aggregate the rating numbers. If i understand this correctly, the whole point of class B is just to be an object which shows relationships? I have been able to go between two classes, but when a third "relational" one is there i can't seem to get it to work.
When an operation needs to be performed on a recordset (queryset) basis rather than single record (model), then you should consider custom managers.
Adding extra Manager methods is the preferred way to add “table-level” functionality to your models. (For “row-level” functionality – i.e., functions that act on a single instance of a model object – use Model methods, not custom Manager methods.)
You don't need class B at all. What you need is a ManyToManyField between A and C; that will, behind the scenes, create a table similar to B, but unless you actually need to add fields on that table you're better off not defining it explicitly.
Once you've added the M2M on C, your average_rating method can use it directly:
class C(models.Model)
model_a_s = models.ManyToManyField('A')
def average_rating(self):
return self.model_a_s.all().aggregate(Avg('rating')).values()[0]
(Note, the title of your question is a bit confusing; there are no views involved here at all.)

Django sync one-to-one models

I simplify my code structure, which contains two models:
# created by third part app, not Django one
# but we share same DB, so i have access to this one
class A(models.Model):
title = models.TextField()
# other fields ...
class Meta:
manage = False
class B(models.Model):
model_a = models.OneToOneField(A, related_name='+')
# other fields, to extend model A functionality
Is this a good way to extend third part app model A with my additional fields and methods? Now i have problem to sync this models true one-to-one field. Since I don't have access to trigger model A creation.
In ideal world i should have CarA and CarB. And CarB = CarA relation should be created if CarB exists.
I base this idea on Django 1.5 user extension. Is this clear enough? Or should i do something else?
You could use a property to create the B instance on access if it doesn't exist yet, ie,
class A(models.Model):
title = models.TextField()
# other fields ...
class Meta:
manage = False
#property
def b(self):
if not hasattr(self, "__bcache"):
self.__bcache, created = B.objects.get_or_create(model_a = self)
return self.__bcache
It seems like you're new to both Python and Django so let's explain quickly...
First, the "#property" part: it's a decorator that turns the following function into a computed attribute - IOW you use it as an attribute (myA.b.whatever), and under the hood it turns it into a method call (myA.b().whatever). It's not strictly required here, we would have used an explicit getter (the same method named get_a()) but it's cleaner that way.
Then our method implementation: obviously we don't want to hit the database each time someone looks up A.b, so
first we check if an attribute named __bcache ("b" "cache") is set on the current instance.
if not, we call B.objects.get_or_create(a_model=self) which will either retrieve the existing B instance for this A instance or create one if none exists yet and we store this B instance as self.__bcache so next call will retrieve it directly from __bcache instead of hitting the database.
and finally we return self.__bcache that is now garanteed to exists and point to the related B instance.

Categories