In a Django admin, add an inline of a generic relation - python

Here are my simplified models :
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation)
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Thing(models.Model):
'''
Our 'Thing' class
with a link (generic relationship) to an abstract config
'''
name = models.CharField(
max_length=128, blank=True,
verbose_name=_(u'Name of my thing'))
# Link to our configs
config_content_type = models.ForeignKey(
ContentType,
null=True,
blank=True)
config_object_id = models.PositiveIntegerField(
null=True,
blank=True)
config_object = GenericForeignKey(
'config_content_type',
'config_object_id')
class Config(models.Model):
'''
Base class for custom Configs
'''
class Meta:
abstract = True
name = models.CharField(
max_length=128, blank=True,
verbose_name=_(u'Config Name'))
thing = GenericRelation(
Thing,
related_query_name='config')
class FirstConfig(Config):
pass
class SecondConfig(Config):
pass
And Here's the admin:
from django.contrib import admin
from .models import FirstConfig, SecondConfig, Thing
class FirstConfigInline(admin.StackedInline):
model = FirstConfig
class SecondConfigInline(admin.StackedInline):
model = SecondConfig
class ThingAdmin(admin.ModelAdmin):
model = Thing
def get_inline_instances(self, request, obj=None):
'''
Returns our Thing Config inline
'''
if obj is not None:
m_name = obj.config_object._meta.model_name
if m_name == "firstconfig":
return [FirstConfigInline(self.model, self.admin_site), ]
elif m_name == "secondconfig":
return [SecondConfigInline(self.model, self.admin_site), ]
return []
admin.site.register(Thing, ThingAdmin)
So far, I've a Thing object with a FirstConfig object linked together.
The code is simplified: in an unrelevant part I manage to create my abstract Config at a Thing creation and set the right content_type / object_id.
Now I'd like to see this FirstConfig instance as an inline (FirstConfigInline) in my ThingAdmin.
I tried with the django.contrib.contenttypes.admin.GenericStackedInline, though it does not work with my current models setup.
I tried to play around with the fk_name parameter of my FirstConfigInline.
Also, as you can see, I tried to play around with the 'thing' GenericRelation attribute on my Config Model, without success..
Any idea on how to proceed to correctly setup the admin?

According to the Django Docs you have to define the ct_fk_field and the ct_field if they were changed from the default values. So it may be enough to set ct_field to config_content_type.
Hope it works!
edit: Those values have to be declared in the Inline:
class SecondConfigInline(admin.StackedInline):
model = SecondConfig
ct_fk_field = "config_object_id"
ct_field = "config_content_type"
edit2:
I just realized an error in my assumption. Usually you should declare the Foreignkey on the Inline-model. Depending on the rest of your code you could just remove the generic Foreignkey on Thing+the genericRelation on Config and declare a normal Foreignkey on the Config-Basemodel.

This question is old, but I'll give it a try anyway.
I think the solution depends on what kind of relation you intend to create between Thing and your Config subclasses.
many-to-one/one-to-many
The way it is currently set up, it looks like a many-to-one relation: each Thing points to a single Config subclass, and many Things can point to the same Config subclass. Due to the generic relation, each Thing can point to a different model (not necessarily a Config subclass, unless you do some extra work).
In this case I guess it would make more sense to put the inline on the admin for the Config. That is, create a GenericStackedInline for Thing (which has the GenericForeignkey), and add the inline to a ConfigAdmin, which you can then use for all Config subclasses. Also see the example below. The generic inline will then automatically set the correct content_type and object_id.
many-to-many
On the other hand, if you are looking for a many-to-many relation between Thing and each Config subclass, then I would move the GenericForeignkey into a separate many-to-many table (lets call it ThingConfigRelation).
A bit of code says more than a thousand words, so let's split up your Thing class as follows:
class Thing(models.Model):
name = models.CharField(max_length=128)
class ThingConfigRelation(models.Model):
thing = models.ForeignKey(to=Thing, on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, null=True, blank=True,
on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(null=True, blank=True)
config_object = GenericForeignKey(ct_field='content_type',
fk_field='object_id')
Now it does make sense to add an inline to the ThingAdmin. The following is a bare-bones example of an admin that works for both sides of the relation:
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericStackedInline
from .models import Thing, FirstConfig, SecondConfig, ThingConfigRelation
class ConventionalTCRInline(admin.StackedInline):
model = ThingConfigRelation
extra = 0
class GenericTCRInline(GenericStackedInline):
model = ThingConfigRelation
extra = 0
class ThingAdmin(admin.ModelAdmin):
inlines = [ConventionalTCRInline]
class ConfigAdmin(admin.ModelAdmin):
inlines = [GenericTCRInline]
admin.site.register(Thing, ThingAdmin)
admin.site.register(FirstConfig, ConfigAdmin)
admin.site.register(SecondConfig, ConfigAdmin)
Note that we use the conventional inline for the ForeignKey-side of the relation (i.e. in ThingAdmin), and we use the generic inline for the GenericForeignKey-side (in ConfigAdmin).
A tricky bit would be filtering the content_type and object_id fields on the ThingAdmin.
... something completely different:
Another option might be to get rid of the GenericForeignKey altogether and use some kind of single-table inheritance implementation with plain old ForeignKeys instead, a bit like this.

Related

How to set a variable from one class equals to a variable in another class in Django models.py?

I am a new in Django world and I want to link two classes from models.py so that i can set their variables equal to each other. Here is the models.py code:
from django.db import models
from django.core.urlresolvers import reverse
# Create your models here.
class file(models.Model):
title = models.CharField(max_length=250)
FILE_TYPE_CHOICES = (
('audio','Audio'),
('games','Games'),
('videos','Videos'),
('applications','Applications'),
('books','Books/Docs'),
('others','Others')
)
file_type = models.CharField(max_length=10,choices=FILE_TYPE_CHOICES,default='others')
description = models.TextField(max_length=6000)
#uploader_username = ???
def get_absolute_url(self):
return reverse('one:user')
def __str__(self):
return self.title
class user (models.Model):
username= models.CharField(max_length=100)
email=models.EmailField
password= models.CharField(max_length = 100)
user_files = models.ForeignKey(file, on_delete=models.CASCADE)
Here I want to set uploader_username from file class equals tousername from user class.
No, you don't want to do this. You want a ForeignKey from File to User, not the other way round, then you can just access my_file.user.username.
Note, it is a bad idea to define your own user class like this. There can be good reasons for doing so, but if so you must inherit from the abstract base classes in the auth app; failure to do so is a serious security problem as you will be storing passwords in clear text. It doesn't look like you need your own model here; you should remove this class.

Multiple Django Admin Arguments with Extensions

Is there a way to use multiple Django extensions in the admin.site.register() inside admin.py? I'm using "simple-history" and "import-export" extensions, but I can only have one of them in the admin.site.register().
Example: I have a model named, "Cars", that is using the "simple-history" extension so I need admin.site.register(Cars, SimpleHistoryAdmin), as their documentation says it should. I want to use the import/export extension as well to the same "Cars" model, but the admin.site.register() doesn't take multiple arguments for me to add it.
models.py
class Cars(models.Model):
Year = models.CharField(max_length=30)
Make = models.CharField(max_length=30)
Model = models.CharField(max_length=30)
history = HistoricalRecords()
class Meta:
verbose_name_plural = "Car Table"
def __str__(self):
return self.Make
admin.py
class CarResource(resources.ModelResource):
class Meta:
model = Cars
fields = ('id','Year', 'Make', 'Model',)
class CarAdmin(ImportExportModelAdmin):
resource_class = CarResource
pass
#I want to use the import/export extension (code above), along with simple-history
admin.site.register(Cars, CarAdmin)
admin.site.register(Cars, SimpleHistoryAdmin)
I've tried using a proxy and inlines, but the proxy makes a new model which I don't want and when using inlines I get an error saying that it requires a foreign key, but I'm not trying to get the model objects from a different model. Naming them the same model doesn't work because the model is already registered. Any help is much appreciated!
In python, class can have more than one parent. Just inherit from 2 parents at once. But both ImportExportModelAdmin and SimpleHistoryAdmin are inheriting from ModelAdmin, that's not good. There is also ImportExportMixin, we can use it instead of ImportExportModelAdmin, so there will be only one reference to ModelAdmin.
class CarResource(resources.ModelResource):
class Meta:
model = Cars
fields = ('id','Year', 'Make', 'Model',)
class CarAdmin(ImportExportMixin, SimpleHistoryAdmin):
resource_class = CarResource
pass
#I want to use the import/export extension (code above), along with simple-history
admin.site.register(Cars, CarAdmin)

Creating many to many relation with AUTH_USER_MODEL in django via intermediary model

I am trying to create the following models. There is a ManyToMany relation from Entry to AUTH_USER_MODEL via the EntryLike intermediate model.
class BaseType(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
creation_time = models.DateTimeField(auto_now_add=True)
last_update_time = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Title(BaseType):
text = models.CharField(max_length=100)
description = models.TextField()
class EntryLike(BaseType):
entry = models.ForeignKey(Entry)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class Entry(BaseType):
title = models.ForeignKey(Title, on_delete=models.PROTECT)
text = models.TextField()
user = models.ForeignKey(settings.AUTH_USER_MODEL)
liked_by_users = models.ManyToManyField(settings.AUTH_USER_MODEL, through='EntryLike', through_fields=('entry', 'user'))
Running migrations on the above model scheme throws the error: AttributeError:'str' object has no attribute 'meta'.
Any help in resolving this error would be highly appreciated. Am new to Django & Python, but not to Web Development.
The issue is that settings.AUTH_USER_MODEL is almost certainly not a model instance. It's probably a string that constrains the choices another model can make - settings would be a strange place to leave a model definition.
To do a MTM between the user model and your field above you need need to do:
from django.contrib.auth.models import User
class Entry(BaseType):
title = models.ForeignKey(Title, on_delete=models.PROTECT)
text = models.TextField()
user = models.ForeignKey(User)
def __str__(self):
return self.title
I've added the str function so that it gives a more sensible return when you're manipulating it in admin/shell.
I'd also question whether you need the second set of fields (removed here), as you can use select related between the Entry and EntryLike join table, without any duplication of the fields - you can probably go that way, it's just a bit unnecessary.
Lastly, I'd note that the way I'm using it above just uses the default User object that comes with Django - you may wish to customise it. or extend the base class as you've done here with your own models' base class.
(All of this is predicated on AUTH_USER_MODEL not being a model instance - if it is, can you post the model definition from settings.py? )

What's the straightforward way to implement one to many editing in list_editable in django admin?

Given the following models:
class Store(models.Model):
name = models.CharField(max_length=150)
class ItemGroup(models.Model):
group = models.CharField(max_length=100)
code = models.CharField(max_length=20)
class ItemType(models.Model):
store = models.ForeignKey(Store, on_delete=models.CASCADE, related_name="item_types")
item_group = models.ForeignKey(ItemGroup)
type = models.CharField(max_length=100)
Inline's handle adding multiple item_types to a Store nicely when viewing a single Store.
The content admin team would like to be able to edit stores and their types in bulk. Is there a simple way to implement Store.item_types in list_editable which also allows adding new records, similar to horizontal_filter? If not, is there a straightforward guide that shows how to implement a custom list_editable template? I've been Googling but haven't been able to come up with anything.
Also, if there is a simpler or better way to set up these models that would make this easier to implement, feel free to comment.
How about making ItemType a ManyToManyField for Store?
To me it seems logical that if you're changing the ItemTypes available in a Store, you're changing a property of the Store (not the ItemType).
e.g.:
from django.db import models
class ItemGroup(models.Model):
group = models.CharField(max_length=100)
code = models.CharField(max_length=20)
class ItemType(models.Model):
item_group = models.ForeignKey(ItemGroup)
type = models.CharField(max_length=100)
class Store(models.Model):
name = models.CharField(max_length=150)
item_type = models.ManyToManyField(ItemType, related_name="store")
# admin
from django.contrib import admin
class StoreAdmin(admin.ModelAdmin):
list_display=('name', 'item_type',)
list_editable=('item_type',)
for model in [(Store, StoreAdmin), (ItemGroup,), (ItemType,)]:
admin.site.register(*model)
I get an error here:
File "C:\Python27\lib\site-packages\django\contrib\admin\validation.py", line 43, in validate
% (cls.__name__, idx, field))
django.core.exceptions.ImproperlyConfigured: 'StoreAdmin.list_display[1]', 'item_type' is a ManyToManyField which is not supported.
Which I solved by commenting out lines 41-43 in django.contrib.admin.validation:
#if isinstance(f, models.ManyToManyField):
# raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
# % (cls.__name__, idx, field))
Probably not the ideal solution, but it seemed to work for me.

Django: "UserProfileRole.userProfile" must be a "UserProfile" instance

I'm having a strange riddle to solve:
I extended my django-1.4 user-objects with a UserProfile, as described at https://docs.djangoproject.com/en/dev/topics/auth/ and wanted to implement project-specific roles. So my models look like the following:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
projects = models.ManyToManyField(Project, through='UserProjectRole')
[...]
class UserProjectRole(models.Model):
userProfile = models.ForeignKey(UserProfile)
project = models.ForeignKey(Project)
group = models.ForeignKey(Group)
[...]
I needed to pass a css-class, so I created a model-form for UserProjectRole and implemented the userProfile-Field with a widget:
class ProjectRoleForm(forms.ModelForm):
userProfile = forms.ModelMultipleChoiceField(label='Users',
queryset=UserProfile.objects.all(),
widget=forms.SelectMultiple(attrs={'class': 'select-multiple'}))
class Meta:
model = UserProjectRole
The form is presented correctly, however, it's crashing during save-process with the following error
Cannot assign "[<UserProfile: MyUser>]": "UserProjectRole.userProfile" must be a "UserProfile" instance.
Does anyone have an idea?
My guess is it's because you are using a forms.SelectMultiple widget. Which gives you a list of UserProfile instances ( [<UserProfile: MyUser>] ) and not a single UserProfile instance which is of course required to set on a ForeignKey field (UserProjectRole.userProfile). Thus I suggest to try using a forms.Select widget instead.

Categories

Resources