I'm trying to integrate in Django admin the next three related models:
# models.py
class Seminar(models.Model):
title = models.CharField(max_length=128, unique=True)
start_date = models.DateField(db_index=True)
end_date = models.DateField(db_index=True)
class Event(models.Model):
title = models.CharField(max_length=128)
start_date = models.DateTimeField(db_index=True)
end_date = models.DateTimeField(db_index=True)
seminar = models.ForeignKey('Seminar')
class Registration(models.Model):
name = models.CharField(max_length=128)
first_name = models.CharField(max_length=128)
seminar = models.ForeignKey('Seminar')
events = models.ManyToManyField('Event', null=True)
# admin.py
class EventInline(admin.TabularInline):
model = Event
class SeminarAdmin(admin.ModelAdmin):
list_display = ('title', 'start_date', 'end_date')
inlines = [
EventInline,
]
class RegistrationAdmin(admin.ModelAdmin):
list_display = ('seminar', 'name', 'first_name')
As you can see, each seminar may have several events, added from the Seminar admin as inline entries.
My problem is with registrations, since they are:
related to a seminar
can "subscribe" to several events of this seminar
Of course, the admin lists all events and not the subset related to the seminar, so:
is it possible to achieve this from the admin (with as low tweaks as possible)?
is the Registration M2M on Event appropriate or should I relate this two models in a different way?
Thanks!
Well so, just before asking here, I did something similar to Django's auth.User model by using different forms for add/change views. I hoped there was a better solution, obviously not.
So here is what I did:
# models.py
class Registration(models.Model):
# [...] - add form excludes it, should allow blank on events field
events = models.ManyToManyField('Event', blank=True, null=True)
# admin.py
class RegistrationAdmin(admin.ModelAdmin):
change_form = RegistrationChangeForm
def get_form(self, request, obj=None, **kwargs):
defaults = {}
if obj is None:
defaults.update(exclude=('events',))
else:
defaults.update(form=self.change_form)
defaults.update(kwargs)
return super(RegistrationAdmin, self).get_form(request, obj, **defaults)
# forms.py
class RegistrationChangeForm(forms.ModelForm):
class Meta:
model = Registration
exclude = ('seminar',)
def __init__(self, *args, **kwargs):
super(RegistrationChangeForm, self).__init__(*args, **kwargs)
self.fields['events'].choices = Event.objects.filter(seminar=kwargs.get('instance').seminar).values_list('pk','title')
So, when adding we simply ignore events (registration is saved with nulled events) and then, on change a list of events can be selected based on the previously defined seminar which is then ignored (since we can't reload the related events list).
Related
I'm having trouble finding the best way to override and add custom html to an edit/add model form in my Django admin site.
Here are the two models involved here:
Icon model used to store "Font Awesome" Icons:
class Icon(models.Model):
name = models.CharField(max_length=100, null=False)
style = models.CharField(max_length=10, choices=STYLE_CHOICES, null=False)
retired = models.BooleanField(default=False)
def delete(self):
self.retired = True
self.save()
objects = NotRetiredManager()
objects_deleted = DeletedManager()
def __str__(self):
return self.name
Workbook model that holds foreign key reference to the above Icon model:
class Workbook(models.Model):
client = models.ForeignKey(Client, on_delete=models.SET_NULL, null=True)
icon = models.ForeignKey(Icon, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=100)
workbookLink = models.CharField(max_length=1000)
retired = models.BooleanField(default=False)
def delete(self):
self.retired = True
self.save()
objects = NotRetiredManager()
objects_deleted = DeletedManager()
def __str__(self):
return self.name
Here are the overridden admin models for the above models:
class BaseAdmin(AdminImageMixin, admin.ModelAdmin):
def delete_queryset(self, request, queryset):
for obj in queryset:
obj.delete()
#admin.register(Workbook)
class WorkbookAdmin(BaseAdmin):
list_display = ("name", "client")
list_filter = (NameFilter, ClientNameFilter)
ordering = ("name", )
#admin.register(Icon)
class IconAdmin(BaseAdmin):
fields = ("name", "style", "icon_display")
list_display = ("icon_display", "name", "style" )
list_display_links = ("icon_display", "name")
list_filter = (NameFilter, )
ordering = ("name", )
def icon_display(self, obj):
return mark_safe(f'<i class="{obj.style}{obj.name}"></i>')
readonly_fields = ["icon_display"]
Here is a list display of some Icons I have in my database:
Currently, the add/edit page for a Workbook on my Admin Site looks like this:
I would like for that dropdown in that second screenshot to be customized similar to the "Icon Display" column in that first screenshot so that a user would choose from graphical list of icons as opposed to the default choicefield form containing the Icon names.
I've looked into the Django docs as well as similar questions on here such as this Similar Stack Overflow Question; however, I'm not fully understanding the proper way to implement something like this.
I hope the information I provided about my app is useful. Please let me know if you'd like me to provide any additional information, or add any clarifications!
I'm creating my first app with Django and still have a lot to learn, but right now I am completely stuck and need some help. I have a model for Customers and Tickets. I have it so different users can save new customers/tickets and only view their data from the dashboard once logged in. However, when creating a new ticket, there is a dropdown option to select customer for the ticket - and the current user is able to see every users customers.
Here is the code, I'll share more code if needed, but I think this covers what I have going on...
forms.py
class TicketForm(ModelForm):
class Meta:
model = Ticket
fields = ['number', 'customer','date_created','work_description','mechanics','status']
views.py
def createTickets(request):
form = TicketForm()
if request.method == 'POST':
form = TicketForm(request.POST)
if form.is_valid():
newticket = form.save(commit=False)
newticket.shopowner = request.user
newticket.save()
return redirect('tickets')
context = {
'form': form
}
return render(request, 'createticket.html', context)
models.py
class Ticket(models.Model):
def default_number():
no = Ticket.objects.count()
return no + 1
shopowner = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
number = models.CharField(max_length=30, unique=True, default= default_number)
customer = models.ForeignKey(Customer, default=1, on_delete= models.SET_DEFAULT, blank=True)
date_created = models.DateField(default=timezone.now)
work_description = models.TextField(verbose_name="Service Details: ")
mechanics = models.ForeignKey(Mechanic, default=1, on_delete=models.DO_NOTHING, blank=True, verbose_name="Mechanic")
status = models.BooleanField(default=True, verbose_name="Open Ticket")
class Meta:
verbose_name_plural = "Tickets"
I need the Customer foreignkey to only display customers of the current user (or 'shopowner') - same thing for mechanic and eventually vehicle but I can figure those out once I know how to get the customer input to display the correct data.
You'll need to customize your form a bit, in order to modify the queryset for that particular field. We also need to pass a user from the view:
forms.py
class TicketForm(ModelForm):
class Meta:
model = Ticket
fields = ['number', 'customer', 'date_created', 'work_description', 'mechanics', 'status']
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if user:
self.fields['customer'].queryset = Customer.objects.filter(shopowner=user)
views.py
def createTickets(request):
form = TicketForm(user=request.user)
# ...
Exactly how you define the queryset is going to depend on how you've defined the relationship between Customer and Shopowner, but this should give you the right approach.
Here is my model relation (copied from Django 2.1 official documentation - my model is an exact copy of this but only the model names are different.):
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
Here is my admin.py:
class MembershipInLine(admin.StackedInline):
model = Membership
class PersonAdmin(admin.ModelAdmin):
inlines = [
MembershipInLine,
]
I get the field on my Person page like this:
Current view
But instead I would like to have this style of view:
Desired view
I've found filter_horizontal and filter_vertical in the official documentation, but I can't figure out how I can use them with inline. How can I do that?
Edit:
I've tried as described in the docs:
class MembershipInLine(admin.StackedInline):
model = Membership # (and also tried with = Group.members.through)
filter_horizontal = ('group', )
But it throws:
(admin.E020) The value of 'filter_horizontal[0]' must be a many-to-many field.
I'd consult with the auth admin panel for group's permissions. Try something like
class PersonAdmin(admin.ModelAdmin):
search_fields = ()
ordering = ()
filter_horizontal = ('membership',)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == 'membership':
qs = kwargs.get('queryset', db_field.remote_field.model.objects)
# Avoid a major performance hit resolving membership names which
# triggers a content_type load:
kwargs['queryset'] = qs.select_related('content_type')
return super().formfield_for_manytomany(db_field, request=request, **kwargs)
https://github.com/django/django/blob/master/django/contrib/auth/admin.py#L29
I'll start from showing how my model look:
from django.db import models
class Project(models.Model):
#.....
pu = models.ForeignKey('Person', default='', related_name='pu')
se = models.ForeignKey('Person', default='', related_name='se')
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
department = models.ForeignKey('Department', default='')
class Department(models.Model):
abbreviation = models.CharField(default='', max_length=3)
full_name = models.CharField(default='', max_length=20)
I want to keep all persons in one table but in my admin panel I want to show two separated filtered lists:
for pu I want to show only Persons which abbreviation of Department
is 'pu'
for se I want to show only Persons which abbreviation of
Department is 'se'
I searched a lot but I'm very new to django and Python.
How can I achieve it?
django admin work arounds are usually possible, with a bit of hacking.
Try creating a myapp/admin.py file
from django.contrib import admin
from .models import Project, Person
class ProjectAdmin(admin.ModelAdmin):
...
filter_horizontal = ('pu', 'se')
def get_form(self, request, obj=None, **kwargs):
form = super(ProjectAdmin, self).get_form(request, obj, **kwargs)
if obj:
form.base_fields['pu'].queryset = Person.objects.filter(department__abbreviation__startswith='pu')
form.base_fields['se'].queryset = Person.objects.filter(department__abbreviation__startswith='se')
return form
admin.site.register(Project, ProjectAdmin) # register it
I've changed my model.
I won't use Person and Department, I've switched to django's User and Group.
To filter it as I needed i used limit_choices_to.
pu = models.ForeignKey("auth.User", related_name='pu', limit_choices_to={'groups__name': "Purchasing"})
se = models.ForeignKey("auth.User", related_name='se', limit_choices_to={'groups__name': "Sales"})
I based on this answer: Django, filter users by group in a model foreign key
I'm developing RESTFul services with DRF and I have multiple databases depending on the country (see my last question here)
I'm having a problem now with relationships, I have two models: Category and SubCategory:
class SubCategory(models.Model):
objects = CountryQuerySet.as_manager()
id = models.AutoField(primary_key=True,db_column='sub_category_id')
name = models.TextField()
female_items_in_category = models.BooleanField()
male_items_in_category = models.BooleanField()
kids_items_in_category = models.BooleanField()
category = models.ForeignKey('Category')
class Meta:
managed = True
db_table = Constants().SUBCATEGORY
And the serializer is:
class SubCategorySerializer(serializers.ModelSerializer):
category = PrimaryKeyRelatedField(queryset=Category.objects.using('es').all())
class Meta:
model = SubCategory
fields = ('id', 'name','female_items_in_category','male_items_in_category','kids_items_in_category','category')
If I don't set the queryset with the proper country it fails, because it doesn't know where to get the category.
Here the problem
I already set the country in the serializer context (in the ModelViewSet):
def get_serializer_context(self):
return {Constants().COUNTRY: self.kwargs.get(Constants().COUNTRY)}
But I can not find the proper way to get the self.context.get(Constants().COUNTRY) in the serializer.
Do you any have an idea to solve this? Thanks!
Well, I found a solution to my problem: I overwrite the method get_fields in the serializer:
def get_fields(self, *args, **kwargs):
fields = super(SubCategorySerializer, self).get_fields()
country = self.context.get(Constants().COUNTRY)
qs = Category.objects.using(country).all()
fields['category'].queryset = qs
return fields
And that works!