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.)
Related
I'm using django-simple-history:
http://django-simple-history.readthedocs.io/en/latest/
I have a model, which I would like to apply its methods on an historical instance. Example:
from simple_history.models import HistoricalRecords
class Person(models.Model):
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
history = HistoricalRecords()
def fullName(self):
return firstname + lastname
person = Person.objects.get(pk=1) # Person instance
for historyPerson in person.history:
historyPerson.fullName() # wont work.
Since the class HistoricalPerson does not inherit the methods of Person. But using Person methods actually make sense, since they share the same fields..
Any solution for this? I'd prefer something simple, not like duplicating every method in my models for the history instances..
I found another workaround (maybe it's just the addon had been updated and got this feature). It's based on the documentation: adding-additional-fields-to-historical-models
HistoricalRecords field accepts bases parameter which sets a class that history objects will inherit. But you can't just set bases=[Person] inside Person class description, because it's not yet initialized.
So I ended up with an abstract class, which is inherited by both Person class and HistoricalRecords field. So the example from the question would look like:
class AbstractPerson(models.Model):
class Meta:
abstract = True
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
def fullName(self):
return firstname + lastname
class Person(AbstractPerson):
history = HistoricalRecords(bases=[AbstractPerson])
And now history objects can use fullName method.
For anyone else having the same problem, I made it work by calling the method from the original class on the historical record object. So for the example in the question, a solution could be:
for historyPerson in person.history:
Person.fullName(historyPerson)
This works because methods are very much like functions in Python, except that when you call a method on an instance, the instance is implicitly passed as the first parameter for the method. So if you have a class like:
class Foo:
def method(self):
....
doing
f = Foo()
f.method()
is the same as:
f = Foo()
Foo.method(f)
I don't know exactly why simple-history does not copy the original model's methods though. One reason might be that since it allows you to exclude fields to be recorded, having the original methods might not make sense, since a method might not work if it uses fields that are not recorded in the historical record.
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.
How can i override the model manager of a many-to-many field that i have considering the following:
class TermsManager(models.Manager):
def all(self):
return super(TermsManager, self).all().filter(condition_here)
class Term(models.Model):
objects = TermsManager()
name = models.CharField(max_length=255)
class Object(models.Model):
title = models.CharField(max_length=255)
terms = models.ManyToManyField(Term, blank=True)
class Channel(Object):
class Meta:
proxy = True
I also have a class which inherits from TermManager called ChannelTermManager.
How can i override the "terms" field of the Channel model so that
mychannel.terms calls the ChannelTermManager instead of TermManager?
First of all, you shouldn't be overriding all(). If you want to change the default queryset, override get_query_set like so:
class TermsManager(models.Manager):
def get_query_set(self):
return super(TermsManager, self).get_query_set().filter(condition_here)
This is because all() is often omitted when other queryset functions are chained on, and you want your queryset to behave the same whether all() is explicitly called or not.
But even so, what you're doing is still problematic. As explained in the documentation for managers, filtering the default related queryset will affect all sorts of automatic things behind the scenes (such as when dumping data to create backups/fixtures, etc.). You almost definitely do not want this. And you really don't want your related object managers doing this either (by setting use_for_related_fields = True), because you'll be masking what's actually stored in the database, rather than simply detecting out of date data and creating alerts or whatever to clean it up. use_for_related_fields is intended for creating managers that augment the normal capabilities of the vanilla manager, not to filter.
I had a similar situation to yours however, and I handled it like so:
class FilteredTermsManager(models.Manager):
def get_query_set(self):
return super(TermsManager, self).get_query_set().filter(condition_here)
class Term(models.Model):
allTerms = models.Manger() # Establish this as the default/automatic manager
objects = FilteredTermsManager()
name = models.CharField(max_length=255)
This way, I could do all my initial querying on the model through my filtered queryset and it looks like "regular Django", but all relational and behind the scenes queries would work on the unfiltered database. And I could always access the true full set of objects by manually doing Term.allTerms.all().
As for using different managers for different related objects, there's nothing you can really do there. But why not just add Channel specific objects to your custom manager, and simply not call them from methods that operate on get Term querysets from Object?
I have two classes in order:
class A(models):
...
class B(models):
a = models.ManyToManyField(A)
Now I have to change my model to one below:
class A(models):
b = models.ManyToManyField(B)
class B(models):
...
I have to use south migrations. I wanted to create new many to many field in class A, migrate data and delete field from class B. The problem is that both are in same model. So when I put many to many into A class it cannot be seen. Because B declaration is below A. How to solve this problem?
At least SQLAlchemy allows you to use a string instead of a class. Try if django-orm allows that, too.
a = models.ManyToManyField('A')
# ...
b = models.ManyToManyField('B')
Update: According to Django/Python Circular model reference that's exactly the way to go.
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