Django Admin foreign key, more detailed - django

Im trying to customise my admin. I have a model with a foreign key to an another model. I currently have a dropdown that display a list of possible keys to chose from, but the list only shows the title.
model.py
class Equipment(models.Model):
noequipment = models.IntegerField('Equipment #' ,db_column='Noequipment', primary_key=True) # Field name made lowercase.
nom = models.CharField('Name',db_column='Nom', max_length=50, blank=True) # Field name made lowercase.
...
nooffice = models.ForeignKey('Office', db_column='NoOffice', blank=True, null=True, verbose_name='Office') # Field name made lowercase.
...
class Meta:
db_table = 'equipment'
ordering = ('nom',)
def __str__(self):
return self.nom
class Office(models.Model):
nooffice = models.IntegerField(db_column='NoOffice', primary_key=True) # Field name made lowercase.
officename = models.CharField(db_column='OfficeName', max_length=50, blank=True) # Field name made lowercase.
adresse = models.CharField(db_column='Adresse', max_length=50, blank=True) # Field name made lowercase.
ville = models.CharField(db_column='Ville', max_length=50, blank=True) # Field name made lowercase.
codepostal = models.CharField(db_column='CodePostal', max_length=50, blank=True) # Field name made lowercase.
class Meta:
db_table = 'office'
def __str__(self):
return self.officename
All I need is that the admin would show a table with the attribute values on top of the drop down.
also here admin.py (Need special ModelAdmin because im using multiple DB as per https://docs.djangoproject.com/en/1.8/topics/db/multi-db/ )
from django.contrib import admin
from .models import Equipment, Manufacturier, Office, Devicetype, Backdoor, Passage, Systadmin, Applicationadmin, Application, Os
from django import forms
from forms import EquipmentAdminForm, ManufacturierAdminForm, OfficeAdminForm, DevicetypeAdminForm, BackdoorAdminForm, PassageAdminForm, SystadminAdminForm, ApplicationadminAdminForm, ApplicationAdminForm, OsAdminForm
class VCOEModelAdmin(admin.ModelAdmin):
# A handy constant for the name of the alternate database.
using = 'vcoe'
def save_model(self, request, obj, form, change):
# Tell Django to save objects to the 'other' database.
obj.save(using=self.using)
def delete_model(self, request, obj):
# Tell Django to delete objects from the 'other' database
obj.delete(using=self.using)
def get_queryset(self, request):
# Tell Django to look for objects on the 'other' database.
return super(VCOEModelAdmin, self).get_queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
return super(VCOEModelAdmin, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
return super(VCOEModelAdmin, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
class VCOETabularInline(admin.TabularInline):
using = 'vcoe'
def get_queryset(self, request):
# Tell Django to look for inline objects on the 'other' database.
return super(VCOEMTabularInline, self).get_queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
return super(VCOEMTabularInline, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
return super(VCOEMTabularInline, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
class OfficeAdmin(VCOEModelAdmin):
form = OfficeAdminForm
class EquipmentAdmin(VCOEModelAdmin):
form = EquipmentAdminForm
...
admin.site.register(Equipment, EquipmentAdmin)
...
admin.site.register(Office, OfficeAdmin)
...
and forms.py
from django import forms
from .models import Equipment, Manufacturier, Office, Devicetype, Backdoor, Passage, Systadmin, Applicationadmin, Application, Os
class EquipmentAdminForm(forms.ModelForm):
class Meta:
model = Equipment
exclude = ['noequipment']
...
class OfficeAdminForm(forms.ModelForm):
class Meta:
model = Office
exclude = ['nooffice']

I see two possible approaches:
The simplest approach is to make __str__ (or __unicode__, depending on Python version) on your model to include extra information you want to see. E.g.:
class Office(models.Model):
...
def __unicode__(self):
return u"{0.officename} ({0.ville}, {0.addresse})".format(self)
The most flexible but most complicated is to create a custom widget. There are some readily available libraries that may or may not suit your needs and tastes. For example, you may consider taking a look at django-autocomplete-light's ModelChoiceField and it`s autocomplete styling options.
The process isn't trivial, but the overall outline is:
Create your own widget class or take something that's readily available.
Create a ModelForm that uses this widget. Some libraries provide a custom fields that are pre-configured to use the widget, if you have a self-implemented one, you can consider writing a field or using Django's built-in ModelChoiceField with widget argument. Some libraries have their own conventions, if you have own implementation, then you can go with something like:
class EquipmentForm(forms.ModelForm):
class Meta:
model = Equipment
widgets = {
"office": OfficeSelectWidget(...)
}
In your ModelAdmin class refer to your form, so Django would use it instead of automatically-generated one.
If you're just experimenting or learning - and have enough time to spare, I suggest go the complicated route and write everything on your own, so you'd know how things work under the hood. Then never do this again, but use batteries from PyPI. If you just want things quick, take a look at abovementioned django-autocomplete-light library, which should be quite simple to get started. Just follow their tutorials and examples, starting from the basic autocomplete field then extending it with custom styling.

Related

Users to see only their objects in Django

So I have an application where users can create their own Companies. But what I want in the view is for them to see only their entries on the view. I have seen similar questions on this platform but they don't work as expected. Below is my code.
Models.Py
from django.contrib.auth.models import User
class Company (models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
company_name = models.CharField(max_length=100, null=True)
mailing_address = models.CharField(max_length=100, null=True)
physical_address = models.CharField(max_length=100, null=True)
class Meta:
verbose_name_plural = "Companies"
def __str__(self):
return self.company_name
views.py
#login_required(login_url='login')
def company (request):
all_companies = Company.objects.filter(user=request.user)
count= Company.objects.all().count()
context = {'all_companies': all_companies, 'count': count}
return render(request, 'company/company.html', context)
forms.py
class CompanyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CompanyForm, self).__init__(*args, **kwargs)
self.fields['company_name'].widget.attrs = {'class': 'input',}
self.fields['date_created'].widget.attrs = {'class': 'input',}
self.fields['mailing_address'].widget.attrs = {'class': 'input',}
self.fields['physical_address'].widget.attrs = {'class': 'input',}
class Meta:
model = Company
fields = ('company_name', 'date_created', 'mailing_address', 'physical_address',)
The so largely this works to ensure that every user only sees the company they have created. However, I can successfully create the companies from the admin side but a glaring issue appears. I have to manually select users from the form field = users in the admin form as shown in the picture below, to be able to create and save companies. It is the same behaviour on the front end with the form. This doesn't look right.
How can I ensure a company automatically points to the owner (user) who created it, without having to manually force the form to choose the user.
admin page
If you want the user to be added automatically in Django admin, you can override the save_model of its corresponding ModelAdmin:
https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model
If you want it to be populated when users are creating companies using forms, you can set the user attribute like this:
# assuming request.user is available
company_form = form.save(commit=False)
company_form.user = request.user
company_form.save()
Since, user is the foreign key. You can take advantage of
'formfield_for_foreignkey' method in the ModelAdmin class.
This method gets executed for the foreign fields declared in the model. Here, we can check whether it has been executed for the user or not if yes, then we can prepopulate its value. You can customize the admin form by creating ModelAdmin class in admin.py for the Company model
#admin.register(Company)
class CompanyAdmin(admin.ModelAdmin):
form = CompanyForm
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'user':
kwargs['initial'] = request.user.id
return db_field.formfield(**kwargs)
return super(CompanyAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
In the ModelAdmin class you can specify the form class for further customizations.
Note, this will only prepopulate the value, the value of the user can be changed in the form. To avoid this, you can make user field uneditable and readonly field.
So I finally found the solution, at least for the user field in the Company form.
This gives a clear way of doing this: https://docs.djangoproject.com/en/4.0/topics/class-based-views/generic-editing/#models-and-request-user
In views.py: I added form.instance for the field user to ensure it picks the current user and feeds it in the form.
def company_form (request):
form = CompanyForm()
if request.method == 'POST':
# Request files helps upload other files such as images
form = CompanyForm(request.POST, request.FILES)
#This automatically inserts the user without exposing the form field
form.instance.user = request.user
if form.is_valid():
form.save()
return redirect('company')
Then I modified the model field for the user to ensure it is not editable.
models.py
class Company (models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, editable=False)
This ensured no one can edit the user including the admin. That ideally solves 90 percent of my issues.
Appreciate everyone's help on this.

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?

Get value of field from referenced model displayed in Django Admin site

I have two models - Contract and Supplier. Each supplier supplies a type of commodity. These are defined as follows:
class CommodityType(models.Model):
name = models.CharField(max_length=64)
def __unicode__(self):
return self.name
class Supplier(models.Model):
name = models.CharField(max_length=64)
type = models.ForeignKey(CommodityType)
def __unicode__(self):
return self.name
class Meta:
ordering = ['type', 'name']
class Contract(models.Model):
supplier = models.ForeignKey(Supplier)
clientNumber = models.CharField(max_length=32)
def __unicode__(self):
return u'%s, %s' % (self.supplier, self.clientNumber)
I want to have a listing of the Contracts in the Django Admin site. For each of the Contracts, I want to have the type from the referenced Supplier displayed. So, for example, if the associated supplier supplies Electricity, then I want to have that displayed in the listing of Contracts.
However, I cannot seem to find how this is done. I found this answer, but trying that gives me an ImproperlyConfigured error.
How can this be done?
What you probably need is the list_display
class ContractAdmin(admin.ModelAdmin):
list_display('clientNumber', 'supplier')
admin.register(Contract, ContractAdmin)
To allow __ in Admin for foreign key, You can use this snippet
From the snippet:
from django.contrib import admin
from django.db import models
def getter_for_related_field(name, admin_order_field=None, short_description=None):
"""
Create a function that can be attached to a ModelAdmin to use as a list_display field, e.g:
client__name = getter_for_related_field('client__name', short_description='Client')
"""
related_names = name.split('__')
def getter(self, obj):
for related_name in related_names:
obj = getattr(obj, related_name)
return obj
getter.admin_order_field = admin_order_field or name
getter.short_description = short_description or related_names[-1].title().replace('_',' ')
return getter
class RelatedFieldAdminMetaclass(admin.ModelAdmin.__metaclass__):
"""
Metaclass used by RelatedFieldAdmin to handle fetching of related field values.
We have to do this as a metaclass because Django checks that list_display fields are supported by the class.
"""
def __getattr__(self, name):
if '__' in name:
getter = getter_for_related_field(name)
setattr(self, name, getter) # cache so we don't have to do this again
return getter
raise AttributeError # let missing attribute be handled normally
class RelatedFieldAdmin(admin.ModelAdmin):
"""
Version of ModelAdmin that can use related fields in list_display, e.g.:
list_display = ('address__city', 'address__country__country_code')
"""
__metaclass__ = RelatedFieldAdminMetaclass
def queryset(self, request):
qs = super(RelatedFieldAdmin, self).queryset(request)
# include all related fields in queryset
select_related = [field.rsplit('__',1)[0] for field in self.list_display if '__' in field]
# Include all foreign key fields in queryset.
# This is based on ChangeList.get_query_set().
# We have to duplicate it here because select_related() only works once.
# Can't just use list_select_related because we might have multiple__depth__fields it won't follow.
model = qs.model
for field_name in self.list_display:
try:
field = model._meta.get_field(field_name)
except models.FieldDoesNotExist:
continue
if isinstance(field.rel, models.ManyToOneRel):
select_related.append(field_name)
return qs.select_related(*select_related)
#### USAGE ####
class FooAdmin(RelatedFieldAdmin):
# these fields will work automatically:
list_display = ('address__phone','address__country__country_code','address__foo')
# ... but you can also define them manually if you need to override short_description:
address__foo = getter_for_related_field('address__foo', short_description='Custom Name')
Recently, a library called django-related-admin released, which allows you to use foreign key attributes in Django admin change list list_display with '__' so easily, Specifically for this question, how to use this library in admin.py module is as follows:
admin.py
from related_admin import RelatedFieldAdmin
from related_admin import getter_for_related_field
class ContractAdmin(RelatedFieldAdmin):
# these fields will work automatically (and boolean fields will display an icon):
list_display = ('clientNumber','supplier__type__name')
# or you can also define them manually if you need to override short_description or boolean parameter:
supplierType = getter_for_related_field('supplier__type__name', short_description='supplier type', boolean=False)

Django 1.4 : Create UserProfile+User as an atomic action, Roles, and Admin integration

I'm building my first real django application which has different user roles and the normal "User signal that creates an UserProfile" approach is falling short. Could you guys help me out ?
To give out more context, here are the requirements from a functional perspective :
New users are added only from the Admin and will be done by non-tech savy people, so I need a user creation flow thats intuitive and easy.
Each user has a role (Manager, Employee, Salesman, etc) with different needs and fields.
The user list needs to show both User Information and role / profile information (login, email, name, and the extra information on profile)
Initial approach :
So armed with this, I went with the recommended approach of creating a UserProfile 1-to-1 object linked to user, provide the UserProfile with a choice field where the role is set (useful for knowing what i'm dealing with when calling get_profile() ) and subclassed UserProfile into ManagerUserProfile, EmployeeUserProfile, etc.
Problem :
That works for my needs in the frontend (outside of the admin), but setting the signal to create a UserProfile when a User is created is pointless since I don't know what kind of UserProfile should I create based only on user information.
What I'm aiming at is an atomic way of creating a particular User and it's corresponding EmployeeUserProfile/ManagerUserProfile at the same time, and having a neat admin representation.
Some of my ideas:
Hide the UserAdmin and User Profile admins, create AdminModels for EmployeeUserProfile/Manager/etc and inline the User model. That way the person creating the users will see only a "New Manager" link with its corresponding fields. But they may create the UserProfile without a user ? How can i make this atomic ? How do i prevent from deleting the user within or make sure they provide all required info before allowing the profile to be saved ? -> Problem with this approach : I cannot seem to inline the user because it has no PK to UserProfile (it's the other way around).
Again, hide UserAdmin, expose the subclassed profiles Admins, and reverse the signal. When a profile is created, create a corresponding user. But for this I need to be able to provide user fields (username, pass, email, etc) from profiles admin form.
Suggestions ?
Its my first app and maybe there's a neat way for this, but I haven't found it yet.
Thanks for taking the time to read this.
Cheers,
Zeta.
I would suggest that you create a custom form, custom admin view and use that for creating users in one request, with the exact logic you need. You can get an idea of how it's done by looking at django's own custom user creation process here.
I finally found a way to implement what I needed. It may not be the cleanest, and it's open to suggestions, but it may help someone with a similar problem.
Since I needed to create only from the admin, I focused on that and build the following.
forms.py
class RequiredInlineFormSet(BaseInlineFormSet):
"""
Generates an inline formset that is required
"""
def _construct_form(self, i, **kwargs):
"""
Override the method to change the form attribute empty_permitted
"""
form = super(RequiredInlineFormSet, self)._construct_form(i, **kwargs)
form.empty_permitted = False
self.can_delete = False
return form
models.py (i'm not using signals for automatically creating a profile on user creation)
class UserProfile(models.Model):
# This field is required.
user = models.OneToOneField(User)
# Other fields here
[.......]
USER_TYPES = (
('manager', 'Manager'),
('employee', 'Employee'),
)
user_type = models.CharField(blank=True, max_length=10, choices=USER_TYPES)
def __unicode__(self):
return self.user.username
class EmployeeProfile(UserProfile):
[...]
def __init__(self, *args, **kwargs):
super(EmployeeProfile, self).__init__(*args, **kwargs)
self.user_type = 'employee'
class ManagerProfile(UserProfile):
[...]
def __init__(self, *args, **kwargs):
super(ManagerProfile, self).__init__(*args, **kwargs)
self.user_type = 'manager'
class Manager(User):
class Meta:
proxy = True
#app_label = 'auth'
verbose_name = 'manager'
verbose_name_plural = 'managers'
def save(self, *args, **kwargs):
self.is_staff = True
super(Manager, self).save(*args, **kwargs) # Call the "real" save() method.
g = Group.objects.get(name='Managers')
g.user_set.add(self)
class Employee(User):
class Meta:
proxy = True
#app_label = 'auth'
verbose_name = 'employee'
verbose_name_plural = 'employees'
def save(self, *args, **kwargs):
self.is_staff = False
super(Employee, self).save(*args, **kwargs) # Call the "real" save() method.
g = Group.objects.get(name='Employees')
g.user_set.add(self)
admin.py
class ManagerProfileAdmin(admin.StackedInline):
model = ManagerProfile
max_num = 1
extra = 1
formset = RequiredInlineFormSet
class EmployeeProfileAdmin(admin.StackedInline):
model = EmployeeProfile
max_num = 1
extra = 1
formset = RequiredInlineFormSet
class ManagerAdmin(UserAdmin):
"""
Options for the admin interface
"""
inlines = [ManagerProfileAdmin]
def queryset(self, request):
qs = super(UserAdmin, self).queryset(request)
qs = qs.filter(Q(userprofile__user_type='manager'))
return qs
class EmployeeAdmin(UserAdmin):
"""
Options for the admin interface
"""
inlines = [EmployeeProfileAdmin]
def queryset(self, request):
qs = super(UserAdmin, self).queryset(request)
qs = qs.filter(Q(userprofile__user_type='employee'))
return qs
admin.site.unregister(User)
admin.site.register(Manager, ManagerAdmin)
admin.site.register(Employee, EmployeeAdmin)

Add multiple records at once in django admin panel

I have following setup.
from django.db import models
from django.contrib.auth.models import User
class Event(models.Model):
name = models.CharField(max_length=64)
date = models.DateField()
ATTENDANCE_CHOICES = (
('A','Attending'),
('N','Absent'),
('L','Taken ill'),
)
class Attendance(models.Model):
student = models.ForeignKey(User)
event = models.ForeignKey(Event)
status = models.CharField(max_length=1, choices=ATTENDANCE_CHOICES)
In a nutshell: Students(User) attend or doesn't attend classes(Event), this is registered by Attendance.
Problem is adding those attendance records one at a time.
What I am looking for is a way to provide form for each class(each Event object) with list of all students and attendance status radio buttons or drop downs next to them.
Something like this:
http://i.imgur.com/jANIZ.png
I have looked at many discussions about multiple/bulk record insertion via django admin and am beginning to wonder is this even possible with django admin or do I have to create such form from scratch? Either way, what would be the best (most django-ish) approach?
"Is this even possible?" It's possible right out of the box.
Look into the django Admin app, Inlines, ModelForms, and the RadioSelect widget.
class MyForm(forms.ModelForm):
class Meta:
model = Attendance
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs
self.fields['status'].widget = forms.RadioSelect(choices=self.fields['status'].choices)
class AttendanceInline(admin.TabularInline):
model = Attendance
form = MyForm
class EventAdmin(admin.ModelAdmin):
inlines = [AttendanceInline]
def save_model(self, request, obj, form, change):
obj.save()
for user in User.objects.all():
obj.attendance_set.create(user=user, status='')
# you should consider a null field or a possible choice for "Undecided"
admin.site.register(Event, EventAdmin)