I'm facing the following situation: I have a django project, which uses an outside app [App1]. Within App1, it has the following structure:
abstract class 'Base':
class Base(models.Model):
"""
Base model with boilerplate for all models.
"""
name = models.CharField(max_length=200, db_index=True)
alternate_names = models.TextField(null=True, blank=True,
default='')
..............
..............
class Meta:
abstract = True
def __str__(self):
display_name = getattr(self, 'display_name', None)
if display_name:
return display_name
return self.name
abstract class based on 'Base', called 'AbstractClassA':
class AbstractClassA(Base):
display_name = models.CharField(max_length=200)
....
....
class Meta(Base.Meta):
abstract = True
def get_display_name(self):
....
....
return ....
The non abstract class class ClassA(AbstractClassA)
Now, when I do a query in my view for this ClassA, for example:
qs = ClassA.objects.filter(Q(name__icontains=query_term)....)
return qs
I feed this qs into another outside app (autocomplete), so that when I type in 'xxxx' on my web form, the form would give me suggestions on available matches in the DB, based on this qs.
This all works great, the only thing is, the list of potential matches shown to me is the default representation of the ClassA objects, which I traced back to
def __str__(self):
display_name = getattr(self, 'display_name', None)
if display_name:
return display_name
return self.name
defined in the base abstract model I've mentioned earlier. What I want is, to have something else displayed as the list of potential matches (e.g. instead of 'display_name' or 'name', show me 'fieldA' + ';'+ 'fieldB' of each filtered item in qs).
My thought was to override this __str__ method somewhere. But because both the upstream and downstream aspect of my process are done in outside apps that I don't want to modify directly (i.e. copy directly into my Django project and rewrite certain parts), I'm not sure how I could achieve my goal.
Is there any elegant way to do so?
Please let me know if anything is unclear, or if I could provide you with any further information. Thanks!
Another approach besides Monkey Patching is to use Proxy models.
class MyClassA(ClassA):
class Meta:
proxy = True
def __str__(self):
return self.attribute
Then use MyClassA instead of ClassA.
From your question it is not clear if the non-abstract classes are written by you, but what you can do is to create a mixin and add that to the class signature of your concrete classes, such as:
class NiceStrMixin():
def __str__(self):
return self.display_name
then
class ClassA(AbstractClassA, NiceStrMixin):
...
If you don't have access to ClassA either, you can monkey patch AbstractClassA.
Related
Here's my attempt at a generalized natural key model manager. It's like the docs except it tries (unsuccessfully) to determine the natural key field names from the Meta.unique_together attribute.
class NaturalKeyModelManager(Manager):
def get_by_natural_key(self, *args):
field_dict = {}
for i, k in enumerate(self.model.Meta.unique_together[0]):
field_dict[k] = args[i]
return self.get(**field_dict)
If I insert a debug print just before the for loop like this:
print dir(self.model.Meta)
it doesn't list the unqiue_together attribute at all:
['__doc__', '__module__', 'abstract']
The 'abstract' bit worried me, but another debug print shows that the model I'm trying manage with natural keys is not abstract:
>>> print self.model.Meta.abstract
False
I am mixing in a lot of abstract base classes. Could that be the problem?
class MixedModel(NamedModel, TimeStampedModel, VersionedModel, Model):
objects = NaturalKeyModelManager()
class Meta:
unique_together = (('name', 'version',),)
For completeness here's one of the mixins:
class TimeStampedModel(Model):
created = DateTimeField(_("Created"), auto_now_add=True, null=True, editable=False)
updated = DateTimeField(_("Updated"), auto_now=True, null=True, editable=True)
class Meta:
abstract = True
The hard-coded model manager works just fine:
class MixedModelManager(Manager):
def get_by_natural_key(self, name, version):
return self.get(name=name, version=version)
In order to get the actual options passed to meta, you should use self.model._meta rather than self.model.Meta
I have a normal model and an abstract model like so:
class TaggedSubject(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
category = models.CharField(max_length=200)
foo = models.CharField(max_length=50)
bar = models.CharField(max_length=50)
# etc
content_type = models.ForeignKey(ContentType)
content_object_pk = models.CharField(max_length=255)
content_object = generic.GenericForeignKey("content_type", "content_object_pk")
def __unicode__(self):
if self.user:
return "%s" % (self.user.get_full_name() or self.user.username)
else:
return self.label
class Taggable(models.Model):
tagged_subjects = generic.GenericRelation(TaggedSubject, content_type_field='content_type', object_id_field='content_object_pk')
#property
def tagged_users(self):
return User.objects.filter(pk__in=self.tagged_subjects.filter(user__isnull=False).values("user"))
class Meta:
abstract = True
The Taggable abstract model class then gets used like so:
class Photo(Taggable):
image = models.ImageField(upload_to="foo")
# ... etc
So if we have a photo object:
photo = Photo.objects.all()[0]
I can all the users tagged in the photo with photo.tagged_users.all()
I want to add the inverse relation to the user object, so that if I have a user:
user = User.objects.filter(pk__in=TaggedSubject.objects.exclude(user__isnull=True).values("user"))[0]
I can call something like user.tagged_photo_set.all() and have it return all the photo objects.
I suspect that since TaggedSubject connects to the Taggable model on a generic relation that it won't be possible to use it as a through model with a ManyToMany field.
Assuming this is true, this is the function I believe I'd need to add (somehow) to the User model:
def tagged_photo_set(self):
Photo.objects.filter(pk__in=TaggedSubject.objects.filter(user=self, content_type=ContentType.objects.get_for_model(Photo))
I'm wondering if it's possible to set it up so that each time a new model class is created based on Taggable, it creates a version of the function above and adds it (ideally as a function that behaves like a property!) to User.
Alternatively, if it is somehow possible to do ManyToMany field connections on a generic relation (which I highly doubt), that would work too.
Finally, if there is a third even cooler option that I am not seeing, I'm certainly open to it.
You could use add_to_class and the class_prepared signal to do some post processing when models subclassing your base class are set up:
def add_to_user(sender, **kwargs):
def tagged_FOO_set(self):
return sender.objects.filter(pk__in=TaggedSubject.objects.filter(
user=self,
content_type=ContentType.objects.get_for_model(sender)))
if issubclass(sender, MyAbstractClass):
method_name = 'tagged_{model}_set'.format(model=sender.__name__.lower())
User.add_to_class(method_name, property(tagged_FOO_set))
class_prepared.connect(add_to_user)
I'm using GeoDjango to search for a bunch of locations of different types. For example, both House and Appartment models are subclasses of Location.
Using the Subclassing Queryset below, I'm able to do something like Location.objects.all() and have it return to me [<House: myhouse>, <House: yourhouse>, <Appartment: myappartment>], which is my desire.
However, I also want to determine the distance to each location. Normally, without the Subclassing Queryset, the code from Exhibit 2 returns for me the distances from the given point to each location.... [ (<Location: Location object>, Distance(m=866.092847284))]
However, if I try to find the distances using the Subclassing Querysets, I get an error such as:
AttributeError: 'House' object has no attribute 'distance'
Do you know how I can preserve the ability return a queryset of subclassed objects yet have the distance property available on the subclass objects? Any advice is much appreciated.
Exhibit 1:
class SubclassingQuerySet(models.query.GeoQuerySet):
def __getitem__(self, k):
result = super(SubclassingQuerySet, self).__getitem__(k)
if isinstance(result, models.Model) :
return result.as_leaf_class()
else :
return result
def __iter__(self):
for item in super(SubclassingQuerySet, self).__iter__():
yield item.as_leaf_class()
class LocationManager(models.GeoManager):
def get_query_set(self):
return SubclassingQuerySet(self.model)
class Location(models.Model):
content_type = models.ForeignKey(ContentType,editable=False,null=True)
objects = LocationManager()
class House(Location):
address = models.CharField(max_length=255, blank=True, null=True)
objects = LocationManager()
class Appartment(Location):
address = models.CharField(max_length=255, blank=True, null=True)
unit = models.CharField(max_length=255, blank=True, null=True)
objects = LocationManager()
Exhibit 2:
from django.contrib.gis.measure import D
from django.contrib.gis.geos import fromstr
ref_pnt = fromstr('POINT(-87.627778 41.881944)')
location_objs = Location.objects.filter(
point__distance_lte=(ref_pnt, D(m=1000)
)).distance(ref_pnt).order_by('distance')
[ (l, l.distance) for l in location_objs.distance(ref_pnt) ] # <--- errors out here
I'm busy trying to solve this one to. How about this:
class QuerySetManager(models.GeoManager):
'''
Generates a new QuerySet method and extends the original query object manager in the Model
'''
def get_query_set(self):
return super(QuerySetManager, self).get_query_set()
And the rest can follow from this DjangoSnippet.
You have to reassign the manager in all subclasses.
From Django documentation:
Managers defined on non-abstract base classes are not inherited by child classes. If you want to reuse a manager from a non-abstract base, redeclare it explicitly on the child class. These sorts of managers are likely to be fairly specific to the class they are defined on, so inheriting them can often lead to unexpected results (particularly as far as the default manager goes). Therefore, they aren't passed onto child classes.
https://docs.djangoproject.com/en/dev/topics/db/managers/#custom-managers-and-model-inheritance
I have one model, and 3 different forms that use this model (of course, each form have different fields of this model). I wrote several clean function to valid the form fill... But, I really dont want copy and past this validation to all forms.
Is it possible to have one common cleaning class? How can I call it?
Here is the actual code:
models.py
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
cpf = models.CharField('CPF', max_length=14, blank=True)
cnpj = models.CharField('CNPJ', max_length=18, blank=True)
...
forms.py
class yyyyyForm(UserCreationForm):
...
def Meta:
...
def Save:
...
def clean_cpf(self):
...
class xxxxxForm(UserCreationForm):
...
def Meta:
...
def Save:
...
def clean_cpf(self):
...
Why don't you have one baseForm class where you put the clean_cpf() method and then extend that for the other forms, and since clean_cpf is in the parent form, you shouldn't have to implement it in the child classes.
In your example it looks like you have a base class called UserCreationForm if that is your form that you can edit then you can put your clean method there.
I am trying to solve problem related to model inheritance in Django. I have four relevant models: Order, OrderItem which has ForeignKey to Order and then there is Orderable model which is model inheritance superclass to children models like Fee, RentedProduct etc. In python, it goes like this (posting only relevant parts):
class Orderable(models.Model):
real_content_type = models.ForeignKey(ContentType, editable=False)
objects = OrderableManager()
available_types = []
def save(self, *args, **kwargs):
"""
Saves instance and stores information about concrete class.
"""
self.real_content_type = ContentType.objects.get_for_model(type(self))
super(Orderable, self).save(*args, **kwargs)
def cast(self):
"""
Casts instance to the most concrete class in inheritance hierarchy possible.
"""
return self.real_content_type.get_object_for_this_type(pk=self.pk)
#staticmethod
def register_type(type):
Orderable.available_types.append(type)
#staticmethod
def get_types():
return Orderable.available_types
class RentedProduct(Orderable):
"""
Represent a product which is rented to be part of an order
"""
start_at = models.ForeignKey(Storage, related_name='starting_products',
verbose_name=_('Start at'))
real_start_at = models.ForeignKey(Storage, null=True,
related_name='real_starting_products', verbose_name=_('Real start at'))
finish_at = models.ForeignKey(Storage, related_name='finishing_products',
verbose_name=_('Finish at'))
real_finish_at = models.ForeignKey(Storage, null=True,
related_name='real_finishing_products', verbose_name=_('Real finish at'))
target = models.ForeignKey(Product, verbose_name=_('Product'))
Orderable.register_type(RentedProduct)
class OrderItem(BaseItem):
unit_price = models.DecimalField(max_digits=8, decimal_places=2,
verbose_name=_('Unit price'))
count = models.PositiveIntegerField(default=0, verbose_name=_('Count'))
order = models.ForeignKey('Order', related_name='items',
verbose_name=_('Order'))
discounts = models.ManyToManyField(DiscountDescription,
related_name='order_items', through=OrderItemDiscounts, blank=True,
verbose_name=_('Discounts'))
target = models.ForeignKey(Orderable, related_name='ordered_items',
verbose_name=_('Target'))
class Meta:
unique_together = ('order', 'target')
I would like to have an inline tied to Order model to enable editing OrderItems. Problem is, that the target field in OrderItem points to Orderable (not the concrete class which one can get by calling Orderable's cast method) and the form in inline is therefore not complete.
Does anyone have an idea, how to create at least a bit user-friendly interface for this? Can it be solved by Django admin inlines only, or you would suggest creating special user interface?
Thanks in advance for any tips.
Try inherit OrderItemInlineAdmin's Form a define your own Form there. But fingers crossed for that.
I'm looking for a solid answer to this very thing, but you should check out FeinCMS. They are doing this quite well.
See, for example, the FeinCMS inline editor. I need to figure out how to adapt this to my code.