Conditional field in Django (Admin) - python

I've been using the Django ORM to model my database/objects, and Django Admin to render my admin pages (including nested admin pages).
I've been meaning to implement the following, but without success: I have a 'project' model. Then I have 'file' model, with a ForeignKey relationship to 'project'. The file model has a 'filetype' field.
SUPPORTED_FILE_TYPES = (
('csv', 'csv'),
('xls', 'xls'),
)
class Project(models.Model):
name = models.CharField(max_length=200)
class File(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
filetype = models.CharField(
max_length=20,
choices=SUPPORTED_FILE_TYPES,
default=SUPPORTED_FILE_TYPES[0],
)
Then in Django admin, I just nestedAdmin to make sure I can add files right from the project view.
class FileInline(nested_admin.NestedStackedInline):
model = File
extra = 0
inlines = [ColumnInline]
class ProjectAdmin(nested_admin.NestedModelAdmin):
inlines = [FileInline]
formfield_overrides = {
models.ManyToManyField: {'widget': CheckboxSelectMultiple},
}
Now a 'file' can be either a excel or a csv file. Each has different attributes (e.g. an excel file has 'sheets', whereas a csv has a 'delimiter' and 'thousand_seperator' fields). I want that, when I add a file and select which filetype it is, to have form fields appear that belong to the filetype I selected. I've been struggling with how to implement this. Several options crossed my mind:
Using GenericForeignKeys, but I didn't manage to get the desired result: the admin lets me choose a model, but not enter the fields of selected model.
class ExcelFile(models.Model):
sheet_name = models.CharField(max_length=100)
class CsvFile(models.Model):
thousand_separator = models.CharField(max_length=1)
delimiter = models.CharField(max_length=1)
class File(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
filename = models.CharField(max_length=200)
file_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('file_type', 'object_id')
Using 'django-polymorphic' to have Excel and CSV subclasses from a parent file model - but that didn't play nice with nested admin
Am I missing some obvious way to model this relation? What would be the preferred approach?

Related

Sort a displayed column defined by a custom model method in the Django admin interface

I want to be able to sort a table column defined using a custom method in the Django admin.
I narrowed down the problem to this simple example in Django:
models.py:
from django.db import models
class MyObject(models.Model):
name = models.CharField(_("name"), max_length=255)
layers = models.URLField(_("Layers"), blank=True, max_length=1024)
choices = models.TextField(
verbose_name=_("Choice values"),
blank=True,
help_text=_("Enter your choice"),
)
class Meta:
verbose_name = _("Object config")
verbose_name_plural = _("Objects config")
def __str__(self): # my custom method
return self.name
and admin.py:
from django import forms
from django.contrib import admin
class MyObjectAdminForm(forms.ModelForm):
"""Form"""
class Meta:
model = models.MyObject
fields = "__all__"
help_texts = {
"layers": "URL for the layers",
}
class MyObjectAdmin(admin.ModelAdmin):
form = MyObjectAdminForm
list_filter = ["name",]
search_fields = ["name",]
# I want the first column (__str__) to be sortable in the admin interface:
list_display = ["__str__", ...] # the ... represent some other DB fields
but for the moment I cannot sort that first column (it is grayed out, I cannot click on its title):
So how could I sort the first column in this admin table as defined by the __str__() method of the MyObject model? (please note that I cannot change the model itself. I'm also brand new to Django, so don't hesitate to detail your answer as if you were speaking to a kid.)

How to edit a property in Django Admin?

I have a model with an attribute that is connected to another model as follow:
class Book(models.Model):
synced = models.OneToOneField('SyncedBook'
related_name='internal',
on_delete=models.CASCADE)
# some more attributes here...
#property
def book_address(self)
return self.synced.book_address
However, the book_address is a also a FK in the SyncedBook table as follow:
book_address = models.ForeignKey('Address', db_index=True, null=True, blank=True,
related_name='address_book', on_delete=models.PROTECT)
I don't know and understand how to be able to edit the book_address through the Django admin page in class BookingAdmin(admin.ModelAdmin), even though I have read over the documentation. At first I have the attribute as readonly, but now I want to be able to edit it and save the new address from the Address table. Is there a way to make it happen through the class BookingAdmin(admin.ModelAdmin) and how? Any example and solution would be appreciate
Model properties are typically used for presenting logically defined data for a particular model instance and not necessarily storing data on the model instance itself.
An example of when to use a model property is as follows:
# Defines a product instance
class Product(model.Models):
name = models.CharField(max_length=100)
description = models.TextField()
active = models.BooleanField(default=True)
cost = models.DecimalField(max_digits=5, decimal_places=2)
price = models.DecimalField(max_digits=5, decimal_places=2)
# calculate profits on product
#property
def profit(self)
p = self.price - self.cost
return p
In your case, you are trying to actually be able to modify data against a related model instance within the django admin. To me this sounds like more specifically an Inline (click here for documentation)
So in your case, you would need to create something like the following to your admin.py file:
class SyncedBookInline(admin.TabularInline):
model = BookInline
#admin.Register(Book)
class BookAdmin(admin.ModelAdmin):
# all your model admin settings
inlines = [SyncedBookInline]
Additional Info:
The Inline solution should still work for you. Please see the working code listed below:
models.py:
from django.db import models
class Hero(models.Model):
name = models.CharField(max_length=50)
class HeroAcquaintance(models.Model):
name = models.CharField(max_length=50)
hero = models.OneToOneField(Hero, on_delete=models.CASCADE)
admin.py:
from django.contrib import admin
from .models import *
class HeroAcquaintanceInline(admin.TabularInline):
model = HeroAcquaintance
#admin.register(Hero)
class HeroAdmin(admin.ModelAdmin):
list_display = (
'name',
)
inlines = [HeroAcquaintanceInline]
#admin.register(HeroAcquaintance)
class HeroAcquaintanceAdmin(admin.ModelAdmin):
list_display = (
'name',
)
Screenshot:

Having admin inline for model without direct relation or foreign key

So, I have this these model:
class Media(models.Model):
title = models.CharField(max_length=50, blank=True)
slug = models.SlugField(max_length=70, blank=True, editable=False)
class Meta:
abstract = True
class Photo(Media):
source = models.ImageField(upload_to='uploads/gallery/photo')
class Video(Media):
source = models.FileField(upload_to='uploads/gallery/photo')
class Item(models.Model):
content_type = models.ForeignKey(
ContentType,
limit_choices_to={'model__in': ['photo', ]},
on_delete=models.CASCADE,
)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class Album(Media):
items = models.ManyToManyField(Item)
How can I have an album admin with photo and video as inline so i can upload photo and video when i created an album ?
When I tried making inline for photo and hook it to album admin, I get error "photo has no foreignkey to album", pretty obvious, from there I think there should be a way to link foreignkey needed by album admin with content object from model item.
Note: I specifically doesn't want an item admin. An item are created at model post save signals.
I do not think this works out of the box. But you can use libraries like eg django-smart-selects-generics. Depending on your django version you might need to update some files there.
Installation works with:
pip install django-smart-selects-generic
You also need to have django-smart-selects installed.
Then add both apps in the settings.
INSTALLED_APPS = (
...
'smart_selects',
'smart_generic'
)
And then in your admin.py you can do:
from smart_generic.form_fields import GenericChainedModelChoiceField
from django.forms.models import ModelForm
class TForm(ModelForm):
object_id = GenericChainedModelChoiceField('content_type','content_type',label=u'Content object',queryset=TargetRelation.objects.all())
class Meta:
model = TargetRelation
fields = ['target','content_type']
class TRAdmin(admin.ModelAdmin):
form = TForm
class TRInline(admin.TabularInline):
model = TargetRelation
form = TForm
class PlanetAdmin(admin.ModelAdmin):
inlines=[TRInline,]
Depending on your django version you might need to replace in widgets.py:
Media = ChainedSelect.Media
by
.media = ChainedSelect.Media
And in smart_select views.py add:
import json as simplejson
and replace the return statement by:
return HttpResponse(json, content_type='application/json')

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? )

Overriding formset in TabularInline Django admin form

I'm having trouble overriding the formset on a TabularInline inline of a ModelAdmin object in my admin site. I know you're supposed to have a model associated with a TabularInline object, but I'm not sure how to specify this on the form object used to generate the formset. With the code below, I'm getting "'AppAssetInline.formset' does not inherit from BaseModelFormSet."
class AppAssetForm(forms.ModelForm):
model = App.assets.through
primary = forms.BooleanField()
uuid = forms.CharField()
class AppAssetInline(admin.TabularInline):
model = App.assets.through
AssetFormset = formset_factory(AppAssetForm)
formset = AssetFormset
class AppAdmin(admin.ModelAdmin):
inlines = [AppAssetInline,]
The answer to my question didn't have to do with how I was structuring my forms, but rather how I was joining fields on my models. I had the following structure in my models:
class App(models.Model):
package = models.FileField(upload_to=settings.APP_PACKAGE_ROOT)
assets = models.ManyToManyField('AppAsset', blank=True, null=True)
download_count = models.IntegerField(default=0)
class AppAsset(models.Model):
def __unicode__(self):
return self.asset_file.name
notes = models.CharField(max_length=255, null=True, blank=True)
type = models.CharField(max_length=255, null=True, blank=True)
asset_file = models.FileField(upload_to=settings.APP_PACKAGE_ROOT)
What I did was change the structure such that AppAsset now has a foreign key on App for its assets. After that, I could use the TabularInline on the AppAsset model with no problems. Here are the latest source files:
https://github.com/ridecharge/spout/blob/master/Spout/AppDistribution/models.py
https://github.com/ridecharge/spout/blob/master/Spout/AppDistribution/admin.py
You should use django.forms.models.inlineformset_factory instead of formset_factory

Categories

Resources