Django: conditional ModelAdmin depending on object - django

Let's say I have a base class with, for example:
class Base(models.Model):
name = models.CharField(max_length=50, blank=False, null=False)
value1 = models.CharField(max_length=50)
value2 = models.CharField(max_length=50)
Now, I'm inputting several types of objects into the table, some of which use parts of the data, some of which use other parts, all of them using some common part (name in this example).
I want a complete listing, but I want to have different views when I click into an object, depending on it's type. Changes in the modelAdmin include: one of the classes uses inlines, others don't, list_display varies, one has extra CSS, etc, etc. Basically we're talking about different modelAdmins.
Alternatives I'm thinking: one is that each of those types subclasses Base, i.e.:
class Type1(Base):
pass
class Type2(Base):
pass
and then I define a modelAdmin for each of them, and one for the Base class just to get the table listing everything. In this one I would override the links so they don't go to /app/base/id, but instead to /app/type1/id, /app/type2/id, etc depending on the type. For each of these, I modify the modelAdmins so after saving they go back to /app/type
A different alternative would be having a single model and a single modelAdmin, and overriding every single method I'm using for change_view to consider what type of object it's rendering, i.e., get_inline_instances, get_formsets, whatever I need to modify list_display, list_display_links, list_filter, etc.
The first alternative looks way cleaner to me, although I'm not sure how to modify the link other than defining a method in the modelAdmin with the correct call to reverse and adding that method as a column in list_display.
Is there an easier way I'm missing?. How would you do it?.
Oh, and it HAS to use the admin. I'd rather do this using views, or separate models, but sadly this is the way it has to be. The High Command wants everything in one single table.
Thanks!.
Edit: also, I just found this and it looks good:
http://django-polymorphic.readthedocs.org/en/latest/admin.html

Django-Polymorphic definitely seems the way to go. It's easy to use and automatically gives me the correct modelAdmin when I click through a base object, something I couldn't replicate with Proxies.
Only problem is a table is created for each child class, even if the child class doesn't have any additional fields, and an extra query is performed per child class even though nothing is recovered from it (only column in the table is a foreign key to the base object).
But it works. I can live with that.

Related

Django - getting the original class name in abstact method

What I am trying to do is best described by the following example:
class MyAbstractClass(models.Model):
abstract_field = IntegerField()
class Meta:
abstract = True
def abstract_method(self):
# THE ISSUE LIES IN THE LINE BELOW
ParentClass.objects.filter(..).update(....)
return self
class InheritedClass(MyAbstractClass):
# Field
def my_view(request):
obj = InheritedClass.objects.get(id=1)
obj.save()
return obj
So basically, the question is, is there any way in the abstract_method to tell Django to address the calling class (that is, InheritedClass)?
Technical answer : Well, quite simply yes :
def abstract_method(self):
type(self).objects.filter(..).update(....)
return self
Note that this is Python methods are called with the "calling" object (the object on which the method is invoked) as first parameter, and all attributes lookups will happens on this object and it's class - else inheritance just wouldn't work at all. The only django-specific part here is that Django prevents you from using a ModelManager on a model instance so you need to explicitely get the object's class, which is returned by type(self).
BUT :
Coding style considerations
Django recommands that model methods acting on the whole table should belong to the ModelManager (by contrast with methods acting on the current row only which are to be implemented as plain methods), so since your method obviously acts on the whole table it might be better as a ModelManager method.
I say "might" because there's a grey area, where updating one row implies updating some other rows too - a typical example is when you have a flag that should always only be set for one single record so you also want to unset it on all other records. There's (of course) not enough context in your question to tell which is the right choice here.
you can just do self.objects.filter(...).update(..)
An abstract class just implements methods that are inherited by its concrete class, here InheritedClass. Therefore, everything for methods and fields are available in the inheriting class.
However, for this instance, I would suggest you look at making a custom model manager. Methods in a model are meant to work with that specific row's fields, whereas managers are intended to work table-wide as described at model methods
Define custom methods on a model to add custom “row-level” functionality to your objects. Whereas Manager methods are intended to do “table-wide” things, model methods should act on a particular model instance.
If you have a method doing filtering in a model method, that is a code smell and it belongs in a custom Manager.

Creating Dynamic Forms in Django

I'm working on a project that involves a form with some standard fields and some custom field users define later. The standard forms are defined on a model in models.py. For example:
class Order(models.model):
number = models.TextField()
date = models.DateField()
I then use this model to create a simple model form to make a way to fill in the information. That's pretty standard Django.
The tricky thing is that my users want to be able to add arbitrary fields to the form. They would like to be able to use the Admin interface to basically modify the form and add values to it at run time.
So, they might want a new text field called "Tracking Number" or something like that. The trick is that they only need it sometimes and they want to be able to add it dynamically without rebuilding the whole database.
I can create a fairly simple model to represent the custom fields like so:
class CustomField(models.Model):
type = models.CharField(choices=FIELD_TYPES)
required = models.BooleanField()
I think I can then take the ModelForm for the Order class and extend it to add these custom fields. What I am unsure of is how to link the custom field values back to the Order.
I know this all might sound odd, but in practice it makes sense. Each user has slightly different needs for the form and want to tweak it. If I have to hard code the models to have their specific fields, then I will have to have a fork for each user. That simply doesn't scale. If instead they can simply add the fields through the admin interface, then things are much simpler.
I feel like this is something that is perhaps already solved by someone out there. I simply cannot find a solution. I can't be the only one who has gotten this kind of request right?

ManyToMany relationship between inherited model and its parent

Probably easiest to explain with an example:
class Item(models.Model):
# ...
class ComplexItem(Item):
components = models.ManyToManyField(Item, through='ComponentItem', symmetrical=False, related_name='component_of')
class ComponentItem(models.Model):
# ...
item = models.ForeignKey(ComplexItem)
component = models.ForeignKey(Item, related_name='used_in_items
I would like a table of Items, with a name, price etc. Then I would like to define ComplexItems which are Items in their own right, but they require other Items in varying quantities.
The above causes the following exception in the admin app:
<class 'inventory.models.ComponentItem'> has more than 1 ForeignKey to <class 'inventory.models.ComplexItem'>
I need to override instance methods in ComplexItem and generally seperate the behavior from Item and the inheritance makes sense from a pure data view.
Is there some alternative definition of this relationship? I'd also like to avoid needing 'related_name' on both ComponentItem.component and ComplexItem.components.
You need to go back to the drawing board. While it's probably technically possible for a model to both inherit from and simultaneously be composed of another model, it's going to get sticky quick.
Try making ComplexItem just inherit from models.Model like Item does. Bet you that change alone will fix everything.
The model above actually works fine (I think, I haven't tested and decided against it for the moment). However the table generated for ComplexItem only has one column pointing to Item, which is fairly useless.
The functionality of ComponentItem can still be gotten by defining a ManyToMany relationship from Item to 'self' through ComponentItem.
Defining separate behavior is as easy as creating a Proxy model.
The actual error above came from my admin.Inline not being able to pick the correct foreign key to use for a ComponentItem, which can be solved like this.
I may come back to the inheritance above, but this works for now.

Smarter removing objects with many-to-many relationship in Django admin interface

I'd like to remove some object with many-to-many relationship using Django admin interface. Standard removing also removes all related objects and the list of removed objects displayed on confirmation page. But I don't need to remove related objects!
Assume we have ContentTopic and ContentItem:
class ContentTopic(models.Model):
name = models.CharField()
code = models.CharField()
class ContentItem(models.Model):
topic = models.ManyToManyField(ContentTopic, db_index=True,\
blank=True, related_name='content_item')
So, I'd like to remove ContentTopic instance using Django admin, but I don't need remove all related ContentItems. So, confirmation page should display only ContentTopic instance to remove.
What is the best way to handle this?
This happens so, coz its developed to do so.
If you want to change this behaviour, the one way can be over-riding delete method of django.db.models.Model.
This delete() method actually does two things, first gathering a list of all dependent objects and delete them. So here, you can override it, to get that list of dependent objects, iterating over it and set their reference to None, instead of deleting them. And thus deleting the concerned object cleanly.
May be if you want this behavior throughout, you can extend a class from django.db.models.Models, override delete(), and extend all your models from this new class.

How do I implement a common interface for Django related object sets?

Here's the deal:
I got two db models, let's say ShoppingCart and Order. Following the DRY principle I'd like to extract some common props/methods into a shared interface ItemContainer.
Everything went fine till I came across the _flush() method which mainly performs a delete on a related object set.
class Order(models.Model, interface.ItemContainer):
# ...
def _flush(self):
# ...
self.orderitem_set.all().delete()
So the question is: how do I dynamically know wheter it is orderitem_set or shoppingcartitem_set?
First, here are two Django snippets that should be exactly what you're looking for:
Model inheritance with content type and inheritance-aware manager
ParentModel and ChildManager for Model Inheritance
Second, you might want to re-think your design and switch to the django.contrib content types framework which has a simple .model_class() method. (The first snippet posted above also uses the content type framework).
Third, you probably don't want to use multiple inheritance in your model class. It shouldn't be needed and I wouldn't be surprised if there were some obscure side affects. Just have interface.ItemContainer inherit from models.Model and then Order inherit from only interface.ItemContainer.
You can set the related_name argument of a ForeignKey, so if you want to make minimal changes to your design, you could just have ShoppingCartItem and OrderItem set the same related_name on their ForeignKeys to ShoppingCart and Order, respectively (something like "item_set"):
order = models.ForeignKey(Order, related_name='item_set')
and
cart = models.ForeignKey(ShoppingCart, related_name='item_set')