Adding custom fields to Django admin - python

I have defined my model with various fields.
Some of these are custom fields, which I am using to validate credit card data, using the fields.py file of my app. Source is here.
class Transaction(models.Model):
card_name = models.CharField()
card_number = CreditCardField(required=True)
security_code = VerificationValueField(required=True)
expiry_date = ExpiryDateField(required=True)
I have defined a ModelForm in my forms.py file.
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = "__all__"
And I've added the form to my admin.py file.
class TransactionAdmin(admin.ModelAdmin):
form = TransactionForm
def get_fieldsets(self, *args, **kwargs):
return (
(None, {
'fields': ('card_name', 'card_number'),
}),
)
admin.site.register(Transaction, TransactionAdmin)
However, the custom fields don't seem to be showing in the administration panel. Having done a ton of research, I found this, which would seem to be the solution, except it doesn't work. I've tried all sorts of over things including adding a fields tuple with the missing fields to get it to work, but no dice. And yes I've done plenty of searching.
The error I get when following the solution in the last link is this:
Unknown field(s) (card_number) specified for Transaction. Check fields/fieldsets/exclude attributes of class TransactionAdmin.
Running Django 1.7.4, Python 3.

Change your model / admin / form like this
class Transaction(models.Model):
card_name = models.CharField()
card_number = models.CharField(max_length=40)
expire_date = models.DateTimeField()
card_code = models.CharField(max_length=10)
class TransactionForm(forms.ModelForm):
card_number = CreditCardField(required=True)
expiry_date = ExpiryDateField(required=True)
card_code = VerificationValueField(required=True)
class Meta:
model = Transaction
fields = "__all__"
class TransactionAdmin(admin.ModelAdmin):
form = TransactionForm
admin.site.register(Transaction, TransactionAdmin)
UPDATE:
CreditCardField is a Form field, not a model field. See its usage in the same link that you have posted.
Those fields will come in the form.

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

Django model form with only view permission puts all fields on exclude

Using a custom ModelForm in my default (django admin) change view gives me an empty variable self.fields if the form gets rendered for a user with only view permissions (new in Django 2.1).
This is my code:
# models.py
class Door(ValidateOnSaveMixin, models.Model):
...
motor_type = models.ForeignKey(
MotorType,
on_delete=models.SET_NULL,
default=None,
blank=True,
null=True)
...
door_type = models.CharField(
max_length=3,
choices=DOOR_TYPES,
null=True,
default=None)
...
vehicle_variant = models.ForeignKey(
VehicleVariant,
on_delete=models.CASCADE)
class Meta:
unique_together = ("vehicle_variant", "location", "motor_type")
...
# admin.py
#admin.register(Door)
class DoorAdmin(ImportExportModelAdmin):
form = DoorAdminForm
list_display = ('descriptor', 'get_customer_link', 'get_variant', 'location', 'get_motor_type_link',
'window_type', 'door_type', 'drum_diameter', 'dist_per_motor_rotation')
fields = ('vehicle_variant', 'description', 'location', 'motor_type',
'drum_diameter', 'window_type', 'door_type')
...
# forms.py
class DoorAdminForm(ModelForm):
class Meta:
model = Door
fields = '__all__'
widgets = {
'motor_type': DoorMotorTypeWidget,
}
def __init__(self, *args, **kwargs):
super(DoorAdminForm, self).__init__(*args, **kwargs)
# this line is crashing on access with a user who has only the view permission, as self.fields is empty
self.fields['vehicle_variant'].queryset = VehicleVariant.objects.all().prefetch_related('customer').order_by('customer__name', 'name')
The root cause is related to the exclude attribute of the Meta class in DoorAdminForm.
No matter what i write to the fields/exclude attributes, always all of the model's fields get automatically put to the exclude list and prevent self.fields to be populated. And this makes my code crash.
Of course i can check for 'verhicle_variant' in self.fields but i don't understand the reason for that behaviour. I also couldn't find the part in the django source which populates the exclude attribute with all model fields.
Anybody has an idea if this is intended? Whats the root cause for that behaviour (ignoring the fields and exclude attribute of the Meta class)?
The point where all fields get put into exclude is here:
https://github.com/django/django/blob/cf79f92abee2ff5fd4fdcc1a124129a9773805b8/django/contrib/admin/options.py#L674
I have never used django admin with just 'view' permissions. Haven't really found a lot of information about how this should look like either: django-admin comes with add- and change-views amongst others, but no viewonly-view? Almost looks like it doesn't really know what to do with users that do not have 'change' permission. It directs them to the change_view and then notices that they are not allowed to change anything so it displays an empty form?
I suppose you could just declare all the fields as readonly for a viewonly-view?
UPDATE:
The field vehicle_variant won't be in form.fields as it would still be excluded due to not having change permission, but at least you don't crash on the form's init. I still have no idea what a change_view without change permissions should look like other than everything being readonly.
class DoorAdminForm(ModelForm):
qs = VehicleVariant.objects.prefetch_related('customer').order_by('customer__name', 'name')
model_field = Door._meta.get_field('vehicle_variant')
vehicle_variant = model_field.formfield(queryset=qs)
class Meta:
model = Door
fields = '__all__'
widgets = {
'motor_type': DoorMotorTypeWidget,
}

How to check a form field for a specific attribute in that field's m2m key

I have a form that allows the user to pick several vans (many-to-many relationship). Each van has a boolean attribute named "available". I want to only show the vans whose "available" attribute is set to "True". How do I do this in the forms.py file?
I know that this could possibly be done in the template, but I did not want to create a new form-template with each individual field written out. I wanted to know if this functionality could be done in the forms.py file or in the class based view. I believe that doing it that way would be a bit cleaner. I've look into the validators but I don't think this is the way to go. Maybe I need to run a query set in the form file that checks the attribute before passing it to the form template?
views.py
def post(self, request):
"""Take in user data, clean it, and then post it to the database."""
form = self.form_class(request.POST) # pass in the user's data to that was submitted in form
if form.is_valid():
trip = form.save(commit=False) # create an object so we can clean the data before saving it
# now get the clean and normalize the data
first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name']
trip_start = form.cleaned_data['trip_start']
trip_end = form.cleaned_data['trip_end']
van_used = form.cleaned_data['van_used']
trip.save()
forms.py
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
models.py
class trips(models.Model):
class Meta:
verbose_name_plural = "trips"
van_used = models.ManyToManyField(vans)
class vans(models.Model):
class Meta:
verbose_name_plural = "vans"
vanName = models.CharField(max_length=30, unique=True, blank=False)
available = models.BooleanField(default=True, blank=False)
# set up how the vans will be referenced in the admin page
def __str__(self):
return self.vanName
The final form that is rendered would only show the vans whose "available" attribute is set to True. Thanks in advance.
You have to override queryset for van_used field in form like this.
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['van_used'].queryset = vans.objects.filter(available=True)

Django admin interface: using horizontal_filter with ManyToMany field with intermediate table

I am trying to enhance the django admin interface similar to what has been done in the accepted answer of this SO post. I have a many-to-many relationship between a User table and a Project table. In the django admin, I would like to be able to assign users to a project as in the image below:
It works fine with a simple ManyToManyField but the problem is that my model uses the through parameter of the ManyToManyField to use an intermediary table. I cannot use the save_m2m() and set() function and I am clueless on how to adapt the code below to make it work.
The model:
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
projects = models.ManyToManyField(Project, through='Membership')
class Project(models.Model):
name = models.CharField(max_length=100, unique=True)
application_identifier = models.CharField(max_length=100)
type = models.IntegerField(choices=ProjectType)
...
class Membership(models.Model):
project = models.ForeignKey(Project,on_delete=models.CASCADE)
user = models.ForeignKey(UserProfile,on_delete=models.CASCADE)
# extra fields
rating = models.IntegerField(choices=ProjectType)
...
The code used for the widget in admin.py:
from django.contrib.admin.widgets import FilteredSelectMultiple
class ProjectAdminForm(forms.ModelForm):
class Meta:
model = Project
fields = "__all__" # not in original SO post
userprofiles = forms.ModelMultipleChoiceField(
queryset=UserProfile.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name='User Profiles',
is_stacked=False
)
)
def __init__(self, *args, **kwargs):
super(ProjectAdminForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['userprofiles'].initial = self.instance.userprofile_set.all()
def save(self, commit=True):
project = super(ProjectAdminForm, self).save(commit=False)
if commit:
project.save()
if project.pk:
project.userprofile_set = self.cleaned_data['userprofiles']
self.save_m2m()
return project
class ProjectAdmin(admin.ModelAdmin):
form = ProjectAdminForm
...
Note: all the extra fields from the intermediary model do not need to be changed in the Project Admin view (they are automatically computed) and they all have a default value.
Thanks for your help!
I could find a way of solving this issue. The idea is:
Create new entries in the Membership table if and only if they are new (otherwise it would erase the existing data for the other fields in the Membership table)
Remove entries that were deselected from the Membership table
To do this, I replaced:
if project.pk:
project.userprofile_set = self.cleaned_data['userprofiles']
self.save_m2m()
By:
if project.pk:
# Get the existing relationships
current_project_selections = Membership.objects.filter(project=project)
current_selections = [o.userprofile for o in current_project_selections]
# Get the submitted relationships
submitted_selections = self.cleaned_data['userprofiles']
# Create new relation in Membership table if they do not exist
for userprofile in submitted_selections :
if userprofile not in current_selections:
Membership(project=project,userprofile=userprofile).save()
# Remove the relations that were deselected from the Membership table
for project_userprofile in current_project_selections:
if project_userprofile.userprofile not in submitted_selections :
project_userprofile.delete()

Ordering a Many-To-Many field in Django Admin

Here's my setup:
from django.contrib.auth.models import User
class Product(models.Model):
...
email_users = models.ManyToManyField(User, null=True, blank=True)
...
[elsewhere]
class ProductAdmin(admin.ModelAdmin):
list_display = ('name','platform')
admin.site.register(Product, ProductAdmin)
My main problem is, when I'm viewing the "Product" page in the admin section, email users are not being being ordered by their ID by default, and I'd like that to be ordered by their username.
From what I've read so far, it seems like I need to be adding:
email_users.admin_order_field = 'xxxx'
But I'm not quite sure what the syntax is to access the username.
The answer was referred to in Hao Lian's comment above, essentially, this is what needed to be done:
class ProductAdminForm(ModelForm):
email_users = forms.ModelMultipleChoiceField(queryset=User.objects.order_by('username'))
class Meta:
model = Product
class ProductAdmin(admin.ModelAdmin):
list_display = ('name','platform')
form = ProductAdminForm
admin.site.register(Product, ProductAdmin)
Mine was slightly different in the sense that I required the forms.ModelMultipleChoiceField, whereas the answer provided used forms.ModelChoiceField()
Solution above works well, but in my case I lost all attributes and customizations that my component had by default (like required, label, etc).
In some cases could be better override __init__() method in order to customize only your queryset, nothing else will change.
class ProductAdminForm(ModelForm):
class Meta:
model = Product
fields = '__all__' # required in new versions
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['email_users'].queryset = (
self.fields['email_users'].queryset.order_by('username')
)

Categories

Resources