Django: get_fieldsets Unknown fields - python

I'm facing a little situation here. I have a model named Product that contains basic information about the product, and a model named SpecificProductInfo that contains a pk pointing towards a Product, a pk pointing towards a Language (another model) and other information in the given language
Not only it's not possible to create a SpecificProductInfo by yourself, but whenever you create a Product (in the admin), the SpecificProductInfo fields should appear too (you can't create a product if you don't give its name in every language used by your website for example). For this, I wanted to use the 'get_fieldsets' function. But Im meeting this error:
'Unknown field(s) (name-Chinese, name-English) specified for Product. Check fields/fieldsets/exclude attributes of class ProductAdmin'
The weirdest thing is that it happens one every two times. When I load the admin page, I don't have any mistakes. Then the mistake appears when I click on save. But if I change a meaningless thing in my code (for example I add a space and then I remove it and save the code) and reload the page, it works. But then the next time it won't! I just don't get it!
Here is my code! Thank you for helping me figure it out!
language_fields = collections.OrderedDict()
settings = SiteSettings.objects.get(pk=1)
class ProductAdminForm(forms.ModelForm):
class Meta():
fields = ('picture', 'phone', 'email', 'website', 'notes',)
model=Product
def __init__(self, *args,**kwargs):
super(ProductAdminForm, self).__init__(*args, **kwargs)
for lang in sitesettings.active_languages.all():
name_title = 'name-{0}'.format(lang.code)
name_label = 'Name ({0})'.format(lang.name)
language_fields[lang.id] = (name_title, )
self.fields[name_title] = forms.CharField(label=name_label,max_length=255)
class ProductAdmin(admin.ModelAdmin):
form=ProductAdminForm
def get_fieldsets(self, request, obj=None):
fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
language_index = 1
for lang in language_fields.values():
fieldsets[0][1]['fields'].insert(language_index, lang[0])
language_index = language_index + 1
return fieldsets
admin.site.register (Product, ProductAdmin)
PS: Im working on Django 1.6.0 and Python 3.2.5
PPS: I didnt specify the includes
PPPS: Settings is a model where one of the fields is the active languages (M2M Language).
Thank you very much for your help!
Have a great day

Related

Django form not calling clean_<fieldname> (in this case clean_email)

I couldn't find an answer to the following question, it took me a couple of hours to find out, hence I'm adding it. I'll add my approach of solving it and the answer.
I'm following a YouTube tutorial from This person. For some reason I'm typing the same code, and I checked every single letter. Yet for some reason my cleaning functions aren't called. It's probably something simple, especially since a related question showed something similar. It's probably a framework thing that I get wrong, but I wouldn't know what it is.
Here is the relevant code.
forms.py (complete copy/paste from his Github)
from django import forms
from .models import SignUp
class ContactForm(forms.Form):
full_name = forms.CharField(required=False)
email = forms.EmailField()
message = forms.CharField()
class SignUpForm(forms.ModelForm):
class Meta:
model = SignUp
fields = ['full_name', 'email']
### exclude = ['full_name']
def clean_email(self):
email = self.cleaned_data.get('email')
email_base, provider = email.split("#")
domain, extension = provider.split('.')
# if not domain == 'USC':
# raise forms.ValidationError("Please make sure you use your USC email.")
if not extension == "edu":
raise forms.ValidationError("Please use a valid .EDU email address")
return email
# Final part is ommited, since it's not relevant.
admin.py (typed over from the tutorial)
from django.contrib import admin
# Register your models here.
from .models import SignUp
from .forms import SignUpForm
class SignUpAdmin(admin.ModelAdmin):
list_display = ['__unicode__', 'timestamp', 'updated']
class Meta:
model = SignUp
form = SignUpForm
admin.site.register(SignUp, SignUpAdmin)
After using print statements for a while and reading questions that seemed similar but eventually didn't solve my problem, I decided to look into the source of Django (idea inspired by the most similar question I could find).
Then, I decided to debug the source, since I wanted to know how Django is treating my customized function (thanks to a tutorial + SO answer). In the source I found that the customized functions were called around return super(EmailField, self).clean(value) (line 585, django/forms/fields.py, Django 1.8). When I was stepping through the code I found the critical line if hasattr(self, 'clean_%s' % name): (line 409, django/forms/forms.py, Django 1.8). I checked for the value name which was "email". Yet, the if-statement evaluated as False ((Pdb) p hasattr(self, 'clean_%s' % name)). I didn't understand why, until I figured out that the function name was not registered ((Pdb) pp dir(self)).
I decided to take a look at the whole source code repository and cross-checked every file and then I found that
class Meta:
model = SignUp
form = SignUpForm
means that form / SignUpForm were nested inside the Meta class. At first, I didn't think much of it but slowly I started to realize that it should be outside the Meta class while staying main class (SignUpAdmin).
So form = SignUpForm should have been idented one tab back. For me, as a Django beginner, it still kind of baffles me, because I thought the Meta class was supposed to encapsulate both types of data (models and forms). Apparently it shouldn't, that's what I got wrong.

Django Admin Inlines with many fields, possible to have inline add button create popup?

I've got 3 models:
class Top(models.Model):
toptitle = models.CharField(max_length=255, verbose_name='top title')
class Middle(models.Model):
top = models.ForeignKey(Top)
middletitle = models.CharField(max_length=255, verbose_name='middle title')
importantfield1 = models.TextField(verbose_name='important field 1')
importantfield2 = models.TextField(verbose_name='important field 2')
...
importantfield20 = models.TextField(verbose_name='important field 20')
Now when I'm viewing the Admin page for a Top, I want to see what Middles are related to it (and be able to add and edit Middles from this page or from a link on this page).
I can do that easily enough with inlines. The problem is it becomes unwieldy with so many (required) fields in Middle.
If I specify that the inline is only to show middletitle
class MiddleInline(admin.TabularInline):
model = MiddleInline
fields = ('middletitle',)
extra = 0
I've got two problems. The first is that there is no way for me to get to the page on which I can edit all of the fields for Middles that already exist (without having to go to the Admin menu, selecting Middle and having to find the right Middle there). The second problem is that if I try to add another Middle from this inline, it allows me to create a middle with just a middletitle, but leaving empty all of the required importantfields.
I've been able to deal with the first issue by adding a link to edit the object:
class MiddleInline(admin.TabularInline):
def changeform_link(self,instance):
if instance.id:
changeform_url = reverse(
'admin:myapp_middle_change', args=(instance.id,)
)
return 'Details'
return ''
changeform_link.allow_tags = True
changeform_link.short_description = '' # omit column header
model = MiddleInline
fields = ('middletitle','changeform_link')
extra = 0
but now I'm not sure how to deal with the second problem.
Ideally I'd like the 'Add Another Middle' section to open up a pop up for creating a new Middle (with the Top already set and having/requiring all of the importantfields), which when saved would refresh the inline.
Is there a way of doing this? Am I approaching this entirely wrong?
I'm pretty sure I had somewhat the same issue. My solution was to display two different fieldsets for two different situations.
The ModelAdmin class has a get_fieldsets(self,request, obj=None) functions which i override in my admin.py file.
so something like:
class MiddleInline(admin.TabularInline):
'''your stuff'''
def get_fieldsets(self, request, obj=None):
if obj is None:
fields = list(('''tuple of the fields you want to display if it's a new object'''))
else:
fields = list(('''tuple of the fields you want to display if it's not a new object'''))
return [(None, {'fields': fields})]
I am not quite entirely sure I got your question right, but I hope this can help!

django admin - populate field with callable

I can't find a single example of anyone doing this apart from this example, which doesn't help me other than to know where the code needs to sit.
How to prepopulate UserProfile fields in the Django admin?
so this is my code
class QuoteMaterial(models.Model):
name = models.CharField(_('name'), max_length=255)
content = models.TextField(_('content'),
help_text=_('A static priced item used when doing a job. Selectable when creating a quote. '))
price = models.DecimalField(_('price'), max_digits=6, help_text="not sure if this is before or after VAT yet", decimal_places=2, default="0.00")
def get_companies():
return CompanyProfile.objects.filter(user=request.user)
company = models.ForeignKey(CompanyProfile, default=get_companies)
If its not obvious, im trying in the admin section to populate a dropdown with the available companies that belong to the user that is logged in.
my problem is that i dont know how to pass the request object to "get_companies". anyone know of any examples.
You will have to do this overriding in your admin class that extends the ModelAdmin, not in your class that extends models.Model. Specifically, you need to override formfield_for_foreignkey.
From the docs:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
For your case, it would seem like:
if db_field.name == "company":
kwargs['queryset'] = request.user.company_set.all()
You're mixing up terms.
"Prepopulating" means to fill in a field from another field. It's not how you filter things for the admin popups, since you aren't actually setting the field, but simply limiting choices and letting the user set the field from those.
Aditionally, the default value for a field needs to be a constant, since this is passed down to the database, which can't use a query to set a default.
What you really want is something like the limit_choices_to (docs) parameter for your ForeignKey, but even then, you can't use request for this; it has to work using fields in the model. The reason for this is that, if you based it on the user, then some users would be unable to select the current value set by another user. You don't want company changing itself when the user just wants to change content, for example, just because user doesn't yield the current company in the filter.

Inline-like solution for Django Admin where Admin contains ForeignKey to other model

I have several Customers who book Appointments. Each Appointment has exactly one customer, though a customer can be booked for multiple appointments occurring at different times.
class Customer(model.Model):
def __unicode__(self):
return u'%s' % (self.name,)
name = models.CharField(max_length=30)
# and about ten other fields I'd like to see from the admin view.
class Appointment(models.Model):
datetime = models.DateTimeField()
customer = models.ForeignKey("Customer")
class Meta:
ordering = ('datetime',)
Now when an admin goes to browse through the schedule by looking at the Appointments (ordered by time) in the admin, sometimes they want to see information about the customer who has a certain appointment. Right now, they'd have to remember the customer's name, navigate from the Appointment to the Customer admin page, find the remembered Customer, and only then could browse their information.
Ideally something like an admin inline would be great. However, I can only seem to make a CustomerInline on the Appointment admin page if Customer had a ForeignKey("Appointment"). (Django specifically gives me an error saying Customer has no ForeignKey to Appointment). Does anyone know of a similar functionality, but when Appointment has a ForeignKey('Customer')?
Note: I simplified the models; the actual Customer field currently has about ~10 fields besides the name (some free text), so it would be impractical to put all the information in the __unicode__.
There is no easy way to do this with django. The inlines are designed to follow relationships backwards.
Potentially the best substitute would be to provide a link to the user object. In the list view this is pretty trivial:
Add a method to your appointment model like:
def customer_admin_link(self):
return 'Customer' % reverse('admin:app_label_customer_change %s') % self.id
customer_admin_link.allow_tags = True
customer_admin_link.short_description = 'Customer'
Then in your ModelAdmin add:
list_display = (..., 'customer_admin_link', ...)
Another solution to get exactly what you're looking for at the cost of being a bit more complex would be to define a custom admin template. If you do that you can basically do anything. Here is a guide I've used before to explain:
http://www.unessa.net/en/hoyci/2006/12/custom-admin-templates/
Basically copy the change form from the django source and add code to display the customer information.
Completing #John's answer from above - define what you would like to see on the your changelist:
return '%s' % (
reverse('admin:applabel_customer_change', (self.customer.id,)),
self.customer.name # add more stuff here
)
And to add this to the change form, see: Add custom html between two model fields in Django admin's change_form
In the ModelAdmin class for your Appointments, you should declare the following method:
class MySuperModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if obj:
# create your own model admin instance here, because you will have the Customer's
# id so you know which instance to fetch
# something like the following
inline_instance = MyModelAdminInline(self.model, self.admin_site)
self.inline_instances = [inline_instance]
return super(MySuperModelAdmin, self).get_form(request, obj, **kwargs)
For more information, browser the source for that function to give you an idea of what you will have access to.
https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L423
There is a library you can use it.
https://github.com/daniyalzade/django_reverse_admin
But if you want to use link to object in showing table you can like this code:
def customer_link(self, obj):
if obj.customer:
reverse_link = 'admin:%s_%s_change' % (
obj.customer._meta.app_label, obj.customer._meta.model_name)
link = reverse(reverse_link, args=[obj.customer.id])
return format_html('More detail' % link)
return format_html('<span >-</span>')
customer_link.allow_tags = True
customer_link.short_description = 'Customer Info'
And in list_display:
list_display = (...,customer_link,...)

Django: form values not updating when model updates

I am creating a form that uses MultipleChoiceField. The values for this field are derived from another model. This method works fine, however, I am noticing (on the production server) that when I add a new item to the model in question (NoticeType), the form does not dynamically update. I have to restart the server for the new item to show up on my MultipleChoiceField.
Any changes to the NoticeType model (editing items or creating new ones) do not propagate to the form. After I restart the production server, the updates appear.
Any ideas why this might be ? The relevant portion of the form is below. Thanks.
from django import forms
from django.contrib.auth.models import User
from notification.models import NoticeType
class EditUserProfileForm(forms.Form):
CHOICES = []
for notice in NoticeType.objects.all():
CHOICES.append( (notice.label,notice.display) )
notifications = forms.MultipleChoiceField(
label="Email Notifications",
required=False,
choices=( CHOICES ),
widget=forms.CheckboxSelectMultiple,)
Although mherren is right that you can fix this problem by defining your choices in the __init__ method, there is an easier way: use the ModelMultipleChoiceField which is specifically designed to take a queryset, and updates dynamically.
class EditUserProfileForm(forms.Form):
notifications = forms. ModelMultipleChoiceField(
label="Email Notifications",
required=False,
queryset = NoticeType.objects.all(),
widget=forms.CheckboxSelectMultiple)
My hunch is that the class definition is only being processed once on load rather than for each instantiation. Try adding the CHOICES computation to the init method like so:
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
CHOICES = []
for notice in NoticeType.objects.all():
CHOICES.append( (notice.label, notice.display) )
self.fields['notifications'].choices = CHOICES

Categories

Resources