Django choosing which model to use in Django admin - django

I have three models
models.py
class Cluster(models.Model):
blockOption= (('IMG', 'IMG'),('DESC', 'DESC'),)
block = models.CharField(max_length= 100, choices=blockOption)
order= models.IntegerField(null=True)
post = models.ForeignKey(Post, verbose_name=('Link to Post'), on_delete=models.CASCADE)
class Image(models.Model):
src = models.ImageField(verbose_name=('Imagefile'))
captions= models.CharField(max_length= 100, null=True)
cluster = models.ForeignKey(Cluster, verbose_name=('Link to Post'), on_delete=models.CASCADE)
class Description(models.Model):
text=models.TextField(max_length=400)
cluster = models.ForeignKey(Cluster, verbose_name=('Link to Post'), on_delete=models.CASCADE)
and in admin.py
class ImageAdminInline(TabularInline):
extra = 1
model = Image
class DescriptionAdminInline(TabularInline):
model= Description
#admin.register(Cluster)
class ClusterAdmin(admin.ModelAdmin):
inlines= (ImageAdminInline, DescriptionAdminInline)
I have put ImageAdminInline and DescriptionAdminInline as inlines to ClusterAdmin.
I' m planning to use ClusterAdmin to choose which admin I would be using. I hope it will be something similar to wagtail's streamfield(image above).
Is there any solution that I could use without overriding admin template?
I have tried using Ajax to just hide or show inlines but it was my first time to override django's template so it failed.

You can create two separate admin classes, one for Image and one for Description, and then use the block field in the Cluster model to determine which admin class to use in the Django admin. Here's an example:
class ImageAdmin(admin.ModelAdmin):
pass
#admin.register(Description)
class DescriptionAdmin(admin.ModelAdmin):
pass
#admin.register(Cluster)
class ClusterAdmin(admin.ModelAdmin):
def get_model_admin(self, request, obj=None):
if obj and obj.block == "IMG":
return ImageAdmin
elif obj and obj.block == "DESC":
return DescriptionAdmin
else:
return super().get_model_admin(request, obj)
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
model_admin = self.get_model_admin(request, obj)
if model_admin:
form.base_fields.update(model_admin.get_form(request, obj, **kwargs).base_fields)
return form
This will use the ClusterAdmin as the main admin class for the Cluster model, but it will dynamically use either the ImageAdmin or DescriptionAdmin to display the appropriate fields based on the value of the block field in the Cluster model.

Related

Auto create related model

I am wondering if it's possible to auto create a related model upon creation of the first model.
This is the models
class Team(models.Model):
name = models.CharField(max_length=55)
class TeamMember(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE, null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=False)
So what I want to do is something like this on the 'Team' model
class Team(models.Model):
name = models.CharField(max_length=55)
#on_new.do_this
TeamMember.team = self
TeamMember.user = request.user
TeamMember.save()
I have tried to find any documentation about this. But only found some example about onetoonefields. But nothing about this.
Appreciate any help. Cheers!
I am assuming you are using forms to create team.
There is no direct way to create the TeamMember instance without the current user(via request). request is available in views only(unless you are using special middleware or third party library to access it), so we can send it form and create the user by overriding the save method of the modelform.
So you can try like this:
# Override the model form's save method to create related object
class TeamForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(TeamForm, self).__init__(*args, **kwargs)
class Meta:
model = Team
def save(self, **kwargs):
user = self.request.user
instance = super(TeamForm, self).save(**kwargs)
TeamUser.objects.create(team=instance, user=user)
return instance
And use this form in View:
# Update get_form_kwargs methods in create view
class TeamCreateView(CreateView):
form_class = TeamForm
template = 'your_template.html'
def get_form_kwargs(self):
kw = super(TeamCreateView, self).get_form_kwargs()
kw['request'] = self.request
return kw
Update
(from comments)If you have the user FK availble in Team then you can use it to create TeamMember by overriding the save method. Try like this:
class Team(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL)
name = models.CharField(max_length=55)
def save(self, *args, **kwargs): # <-- Override
instance = super(Team, self).save(*args, **kwargs)
TeamMember.objects.create(user=instance.user, team=instance)
return instance

Django Use Many To Many with Through in Form

I have a data model where I am using a manual intermediate table for a m2m relationship.
Building on the classical example from the django doc:
from django.db import models
INSTRUMENT_CHOICES = (
('guitar', 'Guitar'),
('bass', 'Bass Guitar'),
('drum', 'Drum'),
('keyboard', 'Keyboard'),
)
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Leadership')
def __str__(self):
return self.name
def get_leadership():
return self.leadership_set.first()
class Leadership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
instrument = models.CharField('Playing Instrument', choices=INSTRUMENT_CHOICES,
max_length=15,
null=True,
blank=False)
class Meta:
unique_together = ('person', 'group')
When I create a new group I also want to specify who is going to be the leader, and for this relationship also specify which instrument he will play in that group.
What really confuses me, given also the lack of documentation on this topic is how to handle this kind of relationship in forms.
This is the form I came with:
class InstrumentField(forms.ChoiceField):
def __init__(self, *args, **kwargs):
super().__init__(INSTRUMENT_CHOICES, *args, **kwargs)
class GroupForm(forms.ModelForm):
instrument = InstrumentField(required=False)
class Meta:
model = Group
fields = ['name',
'members'
'instrument'] # This works but it's not correctly initalized in case of edit form
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk is not None: # editing
# PROBLEM: this doesn't work
self.fields["instrument"].initial = self.instance.get_leadership().instrument
def save(self, commit=True):
group = super().save(commit=False)
if commit:
group.save()
if 'instrument' in self.changed_data:
leader = self.cleaned_data.get('members').first()
instrument = self.cleaned_data['instrument']
Leadership.objects.update_or_create(person=leader, group=group, defaults={'instrument': instrument})
return group
As suggested in the django doc I am manually instantiating Leadership objects (see the form save method).
What I couldn't solve is how to populate the instrument field in case of form editing. I try to do this in the __init__: first I check that we are in "edit" mode (the instance has a pk) then I get the relevant Leadership object (see Group.get_leadership) and from that I extract the instrument and I assign it to the fields["instrument"].initial.
This doesn't work.
I could inspect that the initial value was set but then when I render the form the default choice value is shown (the first value of the INSTRUMENT_CHOICES).
What am I missing here?
Is there a better way or a better docs on how to handle m2m with through model in forms?

Django ModelForm not storing foreignkey in modeladmin

I have the following code which is supposed to create a MyConfig object. However, it doesn't as the app_model is always returned as None.
The idea is to choose from a select few contenttypes and then add a key, and the resulting config will trigger a bunch of services. However whenever I save the form, the contenttype stored in the app_model is always None, which is clearly undesirable.
This is in Django1.8
Here is the admin:
class ContentTypeModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "{}-{}".format(obj.app_label, obj.model)
class MyConfigForm(ModelForm):
class Meta:
model = models.MyConfig
fields = '__all__'
def __init__(self, *args, **kwargs):
super(MyConfigForm, self).__init__(*args, **kwargs)
self.fields['app_model'].label = "App Name"
app_model = ContentTypeModelChoiceField(
ContentType.objects.filter(
app_label__startswith='myApp',
model="myModel",
),
empty_label="Choose a model",
)
class MyConfigAdmin(admin.ModelAdmin):
model = models.MyConfig
form = MyConfigForm
list_display = (<display fields>
)
search_fields = (<search fields>
)
excluded_fields = ('app_model')
And here is the model itself:
class MyConfig(models.Model):
app_model = models.ForeignKey(ContentType, null=True)
ref_key = models.CharField(max_length=32, null=True)
To unfortunately somewhat have my tail between my legs. The missing code was the excluded_fields which contained app_model. I thought this removed it from the displayed fields, but it actually removes it from the data you save into the model upon save.
Thanks to everyone who looked into this. Many apologies.

Django Inline Model Admin filter Foreign Field

I have a following problem.
I have 3 models:
class Deal(models.Model):
name = models.CharField(max_length=80)
class Site(models.Model):
name = models.CharField(max_length=80)
deal = models.ForeignKey(Deal)
class Picture(models.Model):
title = models.CharField(max_length=80)
deal = models.ForeignKey(Deal)
site = models.ForeignKey(Site)
I want to make Deal Admin with Site & Picture inline admin models:
class SiteInline(admin.StackedInline):
model = Site
extra = 1
class PictureInline(admin.StackedInline):
model = Picture
extra = 1
class DealAdmin(admin.ModelAdmin):
inlines = [
SiteInline,
PictureInline,
]
What I want to do is when I am selecting Site in Picture admin it shows only sites that I belong to the current Deal i am viewing (if im updating - not creating new one).
I want this to work in admin, I've spent many hours searching web but couldn't find anything useful, please help!
I was trying to do it this way, but I don't know how to access the parent model instance to get the deal id:
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == 'site':
kwargs['queryset'] = Site.objects.filter(deal__id=1)
return super(PictureInline, self).formfield_for_foreignkey(db_field, request=None, **kwargs)
In DTing's variant I see the problem - self.instance.deal is setted in edit mode, but it unsetted in adding mode
I think, you should wrote
try:
self.fields['site'].queryset = Site.objects.filter(deal=self.instance.deal)
except:
self.fields['site'].queryset = Site.objects
instead
Django: accessing the model instance from within ModelAdmin?
class PictureInlineForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PictureInlineForm, self).__init__(*args, **kwargs)
self.fields['site'].queryset = Site.objects.filter(
deal=self.instance.deal)
class PictureInline(admin.ModelAdmin):
form = PictureInlineForm

ModelForms and ForeignKeys

I got the following models:
class Project(models.Model):
name = models.CharField(max_length=50)
class ProjectParticipation(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project)
class Receipt(models.Model):
project_participation = models.ForeignKey(ProjectParticipation)
Furthermore I have the following CreateView:
class ReceiptCreateView(LoginRequiredMixin, CreateView):
form_class = ReceiptForm
model = Receipt
action = 'created'
I now want a dropdown menu where the User can choose the project, the new receipt should be for. The user should only see the project he is assigned to.
How can I do that?
The simply answer is just create a model form read the docs, this is fundamental.
You may also want to look at related names, that way you can get the reverse on the FK.
class ProjectParticipation(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project, related_name='ProjectParticipation')
I found a solution using a ModelChoiceField:
class ProjectModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.project
class ReceiptForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ReceiptForm, self).__init__(*args, **kwargs)
self.fields['project_participation'] = ProjectModelChoiceField(queryset= ProjectParticipation.objects)
class Meta:
model = Receipt
And then in the CreateView:
class ReceiptCreateView(...)
def get_form(self, form_class):
form = super(ReceiptCreateView, self).get_form(form_class)
form.fields['project_participation'].queryset = ProjectParticipation.objects.filter(user=self.request.user)
return form
Is there a solution to filter the query set directly in the ModelForm?