Generic Foreign Keys in Django Admin - django

Is it possible to filter by GenericForeignKey object titles in the Django admin?
I want to filter by program name, either NonSupportedProgram.title or SupportedProgram.title (list_filter = (SOME FIELD HERE)), but can't figure out how?
models.py
class FullCitation(models.Model):
# the software to which this citation belongs
# either a supported software program or a non-supported software program
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class NonSupportedProgram(models.Model):
title = models.CharField(max_length=256, blank = True)
full_citation = generic.GenericRelation('FullCitation')
class SupportedProgram(models.Model):
title = models.CharField(max_length=256, blank = True)
full_citation = generic.GenericRelation('FullCitation')

Perhaps you could use a SimpleListFilter and define your own queryset so that you can get your own results.
More info here:
https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter
class YourCustomListFilter(admin.SimpleListFilter):
title = 'By Program Name'
parameter_name = 'program'
def lookups(self, request, model_admin):
return(
('supported','Supported Programs'),
('nonsupported', 'Non-Supported Programs')
)
def queryset(self, request, queryset):
if self.value() == 'supported':
return queryset.filter(supported__isnull=False)
if self.value() == 'nonsupported':
return queryset.filter(nonsupported__isnull=False)
admin.py
list_filter = (YourCustomListFilter,)
You will need to add related_query_name='supported' and related_query_name='nonsupported' on your GenericRelation declaration to allow querying from the related objects.
More info on querying GenericRelations here:
https://docs.djangoproject.com/en/1.8/ref/contrib/contenttypes/#reverse-generic-relations
This should be a little push on the right direction.

One way I see fit is the following:
You could change your models to the following:
class FullCitaton(models.Model):
# Any Model Fields you want...
program = ForeignKey('Program')
class Program(models.Model):
title = models.CharField(max_length=256, blank = True)
is_supported = models.NullBooleanField()
Note that both SupportedProgram and NonSupportedProgram are in the same Progam model right now. The way to differ them is through the is_supported NullBoolean field.
Then, all you have to do is queryth Program model using the title field.
Now, maybe you didn't do it this way because you have another issue I'm unable to see right now. But, anyway, this should work.
Cheers!

Related

Django admin search function in GenerifForeignkey field with Content_object relation

I am trying to build an admin page that lets admins search through 2 fields of the model "SeasonalitiesCalculated". The fields for my search are called "fruit" and "object_id".
"fruit" is a Foreignkey field and returns the "name" field of the corresponding fruit.
"object_id" is Genericforeignkey field that sometimes points at a UUID in a model called "Countries" (with a "country_name" field: Germany) and sometimes points at a UUID in a model called "AdminZones" (with an "admin_zone_name" field: California)
The problem now is that django seems to not have any standard way of searching through GenericForeignkeys. So I tried defining a search function like this:
class SeasonalitiesCalculatedAdmin(admin.ModelAdmin):
list_per_page = 20
def country_or_admin_zone_name(self, obj):
return obj.country_or_admin_zone_name()
country_or_admin_zone_name.short_description = 'Country or Admin Zone'
def search_field(self, obj):
return obj.search_field()
list_display = ('fruit', 'country_or_admin_zone_name', 'content_type', ...)
search_fields = ('fruit__fruit_name', 'search_fields')
the admin page itself works and it also shows the country or admin zone names and other foreignkey related fields properly since I specified this in the SeasonalitiesCalculated model like this
class SeasonalitiesCalculated(LoggingMixinSeasons, Basemodel):
fruit = models.ForeignKey('IngredientOriginals', on_delete=models.PROTECT, related_name='%(class)s_related_ingredient_original')
content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT)
object_id = models.UUIDField(default=uuid.uuid4, editable=False)
content_object = GenericForeignKey('content_type', 'object_id')
...
class Meta:
managed = True
db_table = 'seasonalities_calculated'
verbose_name_plural = 'Seasonalities Calculated'
constraints = [models.UniqueConstraint(fields=['ingredient_original_id', 'content_type_id', 'object_id',], name='unique_seasonalities_calculated')]
def country_or_admin_zone_name(self):
content_object = self.content_object
if isinstance(content_object, Countries):
return content_object.country_name
elif isinstance(content_object, AdminZones1):
return content_object.admin_zone_1_name
else:
return None
def search_field(self):
content_object = self.content_object
if isinstance(content_object, Countries):
return 'content_object__country_name'
elif isinstance(content_object, AdminZones1):
return 'content_object__admin_zone_1_name'
return None
but i just cant figure out how to make the search_fields work becuase of the content object relation.
i tried overriding the standard search query but without success...
any help is much appreciated

Django ModelChoiceField Issue

I've got the following Situation, I have a rather large legacy model (which works nonetheless well) and need one of its fields as a distinct dropdown for one of my forms:
Legacy Table:
class SummaryView(models.Model):
...
Period = models.CharField(db_column='Period', max_length=10, blank=True, null=True)
...
def __str__(self):
return self.Period
class Meta:
managed = False # Created from a view. Don't remove.
db_table = 'MC_AUT_SummaryView'
Internal Model:
class BillCycle(models.Model):
...
Name = models.CharField(max_length=100, verbose_name='Name')
Period = models.CharField(max_length=10, null=True, blank=True)
Version = models.FloatField(verbose_name='Version', default=1.0)
Type = models.CharField(max_length=100, verbose_name='Type', choices=billcycle_type_choices)
Association = models.ForeignKey(BillCycleAssociation, on_delete=models.DO_NOTHING)
...
def __str__(self):
return self.Name
Since I don't want to connect them via a Foreign Key (as the SummaryView is not managed by Django) I tried a solution which I already used quite a few times. In my forms I create a ModelChoiceField which points to my Legacy Model:
class BillcycleModelForm(forms.ModelForm):
period_tmp = forms.ModelChoiceField(queryset=SummaryView.objects.values_list('Period', flat=True).distinct(),
required=False, label='Period')
....
class Meta:
model = BillCycle
fields = ['Name', 'Type', 'Association', 'period_tmp']
And in my view I try to over-write the Period Field from my internal Model with users form input:
def billcycle_create(request, template_name='XXX'):
form = BillcycleModelForm(request.POST or None)
data = request.POST.copy()
username = request.user
print("Data:")
print(data)
if form.is_valid():
initial_obj = form.save(commit=False)
initial_obj.ModifiedBy = username
initial_obj.Period = form.cleaned_data['period_tmp']
initial_obj.Status = 'Creating...'
print("initial object:")
print(initial_obj)
form.save()
....
So far so good:
Drop Down is rendered correctly
In my print Statement in the View ("data") I see that the desired infos are there:
'Type': ['Create/Delta'], 'Association': ['CP'], 'period_tmp': ['2019-12']
Still I get a Select a valid choice. That choice is not one of the available choices. Error in the forms. Any ideas??

Two step object creation in Django Admin

I'm trying to change the implementation of an EAV model using a JSONField to store all the attributes defined by an attribute_set.
I already figured out how to build a form to edit the single attributes of the JSON, but I'm currently stuck at implementing the creation of a new object. I think I have to split object creation in two steps, because I need to know the attribute_set to generate the correct form, but I don't know if there's a way to hook in the create action, or any other way to achieve what I need.
My models look like this:
class EavAttribute(models.Model):
entity_type = models.CharField(max_length=25, choices=entity_types)
code = models.CharField(max_length=30)
name = models.CharField(max_length=50)
data_type = models.CharField(max_length=30, choices=data_types)
class AttributeSet(models.Model):
name = models.CharField(max_length=25)
attributes = models.ManyToManyField('EavAttribute')
class EntityAbstract(models.Model):
attribute_set = models.ForeignKey(
'AttributeSet',
blank=False,
null=False,
unique=False,
)
class Meta:
abstract = True
class Event(EntityAbstract):
entity_type = models.CharField(max_length=20, null=False, choices=entity_types, default=DEFAULT_ENTITY_TYPE)
code = models.CharField(max_length=25, null=True, blank=True, db_index=True)
year = models.IntegerField(db_index=True)
begin_date = models.DateField()
end_date = models.DateField()
data = JSONField()
How can I choose the AttributeSet first and then go to another form that I would populate with the attributes in the chosen attribute set?
I ended up using get_fields() and response_add() methods, like so:
def get_fields(self, request, obj=None):
if obj is None:
return ['attribute_set']
else:
return [attr.name for attr in obj._meta.get_fields() if not attr.auto_created and attr.name != 'id']
def get_readonly_fields(self, request, obj=None):
readonly_fields = ['entity_type', 'code', 'state']
if obj is not None:
readonly_fields.append('attribute_set')
return readonly_fields
def response_add(self, request, obj, post_url_continue=None):
url = '/admin/risks/event/{}/change/'.format(obj.id)
return redirect(url)
The downside of this approach is that object is saved in the database and then opened for edit, so basically the database is hit twice and all attributes have to be nullable, except for attribute_set.
I would be happy to receive ideas for better implementations.

Try to join a OneToOne relationship in Django

I need some help doing a join using Django, which seems like it should be easy. I have looked at the documentation but it seems like it won't join for some reason.
I am trying to get in my view, the model.Photo and model.PhotoExtended with both joined and then displayed in the view. Currently I am just trying to get the model.Photo displayed but with a join which finds the request.user and filters it based on that.
They are in different apps.
models.py for model.Photo
class Photo(ImageModel):
title = models.CharField(_('title'),
max_length=60,
unique=True)
slug = models.SlugField(_('slug'),
unique=True,
help_text=_('A "slug" is a unique URL-friendly title for an object.'))
models.py for model.PhotoExtended
class PhotoExtended(models.Model):
Photo = models.OneToOneField(Photo, related_name='extended', help_text='Photo required', null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, help_text='User that uploaded the photo')
views.py
class PhotoExtendedUserView(ListView):
template_name = 'photo_user_list.html'
def get_queryset(self):
user = get_object_or_404(User, username=self.request.user)
return Photo.objects.filter(photoextended__user=user)
You set the related_name on Photo (which shouldn't be capitalized by the way) to extended so you need to filter like so:
class PhotoExtendedUserView(ListView):
template_name = 'photo_user_list.html'
def get_queryset(self):
user = get_object_or_404(User, username=self.request.user)
# 'extended' vs. 'photoextended'
return Photo.objects.filter(extended__user=user)

Models unique_together constraint + None = fail?

2 questions:
How can I stop duplicates from being created when parent=None and name is the same?
Can i call a model method from within the form?
Please see full details below:
models.py
class MyTest(models.Model):
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=255, blank=True, unique=True)
owner = models.ForeignKey(User, null=True)
class Meta:
unique_together = ("parent", "name")
def save(self, *args, **kwargs):
self.slug = self.make_slug()
super(MyTest, self).save(*args, **kwargs)
def make_slug(self):
# some stuff here
return generated_slug
note: slug = unique as well!
forms.py
class MyTestForm(forms.ModelForm):
class Meta:
model = MyTest
exclude = ('slug',)
def clean_name(self):
name = self.cleaned_data.get("name")
parent = self.cleaned_data.get("parent")
if parent is None:
# this doesn't work when MODIFYING existing elements!
if len(MyTest.objects.filter(name = name, parent = None)) > 0:
raise forms.ValidationError("name not unique")
return name
Details
The unique_together contraint works perfectly w/ the form when parent != None. However when parent == None (null) it allows duplicates to be created.
In order to try and avoid this, i tried using the form and defined clean_name to attempt to check for duplicates. This works when creating new objects, but doesn't work when modifying existing objects.
Someone had mentioned i should use commit=False on the ModelForm's .save, but I couldn't figure out how to do/implement this. I also thought about using the ModelForm's has_changed to detect changes to a model and allow them, but has_changed returns true on newly created objects with the form as well. help!
Also, (somewhat a completely different question) can I access the make_slug() model method from the Form? I believe that currently my exclude = ('slug',) line is also ignoring the 'unique' constraint on the slug field, and in the models save field, I'm generating the slug instead. I was wondering if i could do this in the forms.py instead?
You could have a different form whether you are creating or updating.
Use the instance kwarg when instantiating the form.
if slug:
instance = MyTest.object.get( slug=slug )
form = MyUpdateTestForm( instance=instance )
else:
form = MyTestForm()
For the second part, I think that's where you could bring in commit=False, something like:
if form.is_valid():
inst = form.save( commit=False )
inst.slug = inst.make_slug()
inst.save()
I don't know for sure this will fix your problem, but I suggest testing your code on the latest Django trunk code. Get it with:
svn co http://code.djangoproject.com/svn/django/trunk/
There have been several fixes to unique_together since the release of 1.02, for example see ticket 9493.
Unique together should be a tuple of tuples
unique_together = (("parent", "name"),)