Django inline for model inheritance target - django

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.

Related

How to I automatically filter out is_deleted records in an associated table in Django?

I am using soft deletes on one of my models in Django, and I am overwriting the default manager to always return active records only, using something like:
class ActiveRecordManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class Tag(models.Model):
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveRecordManager()
class Photo(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="photos")
objects = ActiveRecordManager()
All works well. However, when I do:
tag = Tag.objects.get(pk=100)
And then I try to get the associated photos:
photos = tag.photos.all()
Then I get photos that are deleted. I only want to return objects that are not deleted (so my regular objects list. I was reading about _base_mangers in Django, which seems to control this, but the documentation recommends against filtering objects out:
If you override the get_queryset() method and filter out any rows,
Django will return incorrect results. Don’t do that. A manager that
filters results in get_queryset() is not appropriate for use as a base
manager.
But what I am not clear about is how I am supposed to filter these results. Any thoughts?
UPDATE:
I was asked to explain how this question is different from this one:
How to use custom manager with related objects?
In this 8 year old question they mention a deprecated method. That deprecated method is superseded by the method I outline below (base_managers) which according to the documentation I should not use. If people think I should use it, can you please elaborate?
why not use custom query methods instead of overriding manager as it may produce problems for example in admin pages?
class ActiveModelQuerySet(models.QuerySet):
def not_active(self, *args, **kwargs):
return self.filter(is_deleted=True, *args, **kwargs)
def active(self, *args, **kwargs):
return self.filter(is_deleted=False, *args, **kwargs)
class Tag(models.Model):
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveModelQuerySet().as_manager()
class Photo(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="photos")
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveModelQuerySet().as_manager()
you can then filter your models however you want
tag = Tag.objects.active(pk=100)
deleted_tags = Tag.objects.not_active()
photos = tag.photos.active()
also note that you need is_deleted attribute in all your models that have the soft delete functionality like Photo in your case

override __str__(self) of a model from an imported app

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.

django cbv dynamically exclude field from form based on is_staff / is_superuser

Been trying to determine the "most" elegant solution to dropping a field from a from if the user is not is_staff/is_superuser. Found one that works, with a minimal amount of code. Originally I though to add 'close' to the 'exclude' meta or use two different forms. But this seems to document what's going on. The logic is in the 'views.py' which is where I feel it blongs.
My question: Is this safe? I've not seen forms manipulated in this fashion, it works.
models.py
class Update(models.Model):
denial = models.ForeignKey(Denial)
user = models.ForeignKey(User)
action = models.CharField(max_length=1, choices=ACTION_CHOICES)
notes = models.TextField(blank=True, null=True)
timestamp = models.DateTimeField(default=datetime.datetime.utcnow().replace(tzinfo=utc))
close = models.BooleanField(default=False)
forms.py
class UpdateForm(ModelForm):
class Meta:
model = Update
exclude = ['user', 'timestamp', 'denial', ]
views.py
class UpdateView(CreateView):
model = Update
form_class = UpdateForm
success_url = '/denials/'
template_name = 'denials/update_detail.html'
def get_form(self, form_class):
form = super(UpdateView, self).get_form(form_class)
if not self.request.user.is_staff:
form.fields.pop('close') # ordinary users cannot close tickets.
return form
Yes, your approach is perfectly valid. The FormMixin was designed so you can override methods related to managing the form in the view and it is straightforward to test.
However, should yours or someone else's dynamic modifications of the resulting form object become too extensive, it would probably be best to define several form classes and use get_form_class() to pick the correct form class to instantiate the form object from.

Django: Adding property to User model after creating model based on abstract class

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)

Setting default value for Foreign Key attribute

What is the best way to set a default value for a foreign key field in a model? Suppose I have two models, Student and Exam with student having exam_taken as foreign key. How would I ideally set a default value for it? Here's a log of my effort
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=1)
Works, but have a hunch there's a better way.
def get_exam():
return Exam.objects.get(id=1)
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=get_exam)
But this fails with tables does not exist error while syncing.
Any help would be appreciated.
I would modify #vault's answer above slightly (this may be a new feature). It is definitely desirable to refer to the field by a natural name. However instead of overriding the Manager I would simply use the to_field param of ForeignKey:
class Country(models.Model):
sigla = models.CharField(max_length=5, unique=True)
def __unicode__(self):
return u'%s' % self.sigla
class City(models.Model):
nome = models.CharField(max_length=64, unique=True)
nation = models.ForeignKey(Country, to_field='sigla', default='IT')
As already implied in #gareth's answer, hard-coding a default id value might not always be the best idea:
If the id value does not exist in the database, you're in trouble. Even if that specific id value does exist, the corresponding object may change. In any case, when using a hard-coded id value, you'd have to resort to things like data-migrations or manual editing of existing database content.
To prevent that, you could use get_or_create() in combination with a unique field (other than id).
Here's one way to do it:
from django.db import models
class Exam(models.Model):
title = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=255)
#classmethod
def get_default_pk(cls):
exam, created = cls.objects.get_or_create(
title='default exam',
defaults=dict(description='this is not an exam'),
)
return exam.pk
class Student(models.Model):
exam_taken = models.ForeignKey(
to=Exam, on_delete=models.CASCADE, default=Exam.get_default_pk
)
Here an Exam.title field is used to get a unique object, and an Exam.description field illustrates how we can use the defaults argument (for get_or_create) to fully specify the default Exam object.
Note that we return a pk, as suggested by the docs:
For fields like ForeignKey that map to model instances, defaults should be the value of the field they reference (pk unless to_field is set) instead of model instances.
Also note that default callables are evaluated in Model.__init__() (source). So, if your default value depends on another field of the same model, or on the request context, or on the state of the client-side form, you should probably look elsewhere.
I use natural keys to adopt a more natural approach:
<app>/models.py
from django.db import models
class CountryManager(models.Manager):
"""Enable fixtures using self.sigla instead of `id`"""
def get_by_natural_key(self, sigla):
return self.get(sigla=sigla)
class Country(models.Model):
objects = CountryManager()
sigla = models.CharField(max_length=5, unique=True)
def __unicode__(self):
return u'%s' % self.sigla
class City(models.Model):
nome = models.CharField(max_length=64, unique=True)
nation = models.ForeignKey(Country, default='IT')
In my case, I wanted to set the default to any existing instance of the related model. Because it's possible that the Exam with id 1 has been deleted, I've done the following:
class Student(models.Model):
exam_taken = models.ForeignKey("Exam", blank=True)
def save(self, *args, **kwargs):
try:
self.exam_taken
except:
self.exam_taken = Exam.objects.first()
super().save(*args, **kwargs)
If exam_taken doesn't exist, django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist will be raised when a attempting to access it.
The issue with most of these approaches are that they use HARD CODED values or lambda methods inside the Model which are not supported anymore since Django Version 1.7.
In my opinion, the best approach here is to use a sentinel method which can also be used for the on_delete argument.
So, in your case, I would do
# Create or retrieve a placeholder
def get_sentinel_exam():
return Exam.objects.get_or_create(name="deleted",grade="N/A")[0]
# Create an additional method to return only the id - default expects an id and not a Model object
def get_sentinel_exam_id():
return get_sentinel_exam().id
class Exam(models.Model):
....
# Making some madeup values
name=models.CharField(max_length=200) # "English", "Chemistry",...
year=models.CharField(max_length=200) # "2012", "2022",...
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam",
on_delete=models.SET(get_sentinel_exam),
default=get_sentinel_exam_id
)
Now, when you just added the exam_taken field uses a guaranteed existing value while also, when deleting the exam, the Student themself are not deleted and have a foreign key to a deleted value.
You could use this pattern:
class Other(models.Model):
DEFAULT_PK=1
name=models.CharField(max_length=1024)
class FooModel(models.Model):
other=models.ForeignKey(Other, default=Other.DEFAULT_PK)
Of course you need to be sure that there is a row in the table of Other. You should use a datamigration to be sure it exists.
I'm looking for the solution in Django Admin, then I found this:
class YourAdmin(admin.ModelAdmin)
def get_changeform_initial_data(self, request):
return {'owner': request.user}
this also allows me to use the current user.
see django docs
the best way I know is to use lambdas
class TblSearchCase(models.Model):
weights = models.ForeignKey('TblSearchWeights', models.DO_NOTHING, default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want'))
so you can specify the default row..
default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want')