Django Admin Panel Choices from Database - django

I have a model that has a IntegerField and in the admin. I want to add choices to the widget as "University" field. There is no problem if I add the universities in list as uniList.
But I do not know how can add these universities from UniversityList Class. I want to add new universities to UniversityList on admin panel and then I want to choose these added universities in Mainland2 admin page.
In this case I received error message as
in get raise self.model.MultipleObjectsReturned(mainpage.models.MultipleObjectsReturned: get() returned more than one UniversityList -- it returned 5!
Thank you for in advance...
from django.db import models
from django import forms
from django_countries.fields import CountryField
from django.core.exceptions import ValidationError
class UniversityList(models.Model):
name = models.CharField(max_length=50)
class Mainland2(models.Model):
unilist = [
(0, "---"),
(1, "Uni 1"),
(2,"Uni 2"),
(3,"Uni 3"),
]
graduatedList=[
(0, "Diğer"),
(1, "Lise"),
(2, "Lise (Öğrenci)"),
(3, "Ön Lisans"),
(4, "Ön Lisans (Öğrenci)"),
(5, "Lisans"),
(6, "Lisans (Öğrenci)"),
(7, "Yüksek Lisans"),
(8, "Yüksek Lisans (Öğrenci)"),
(9, "Doktora"),
(10, "Doktora (Öğrenci)")
]
def validate_digit_length(idName):
if not (idName.isdigit() and len(idName) == 11):
raise ValidationError('%(idName)s en az 11 karakter olmalıdır', params={'idName': idName}, )
name = models.CharField(max_length=20, verbose_name="Ad")
midName = models.CharField(max_length=20, verbose_name="Orta Ad", null=False, blank=True)
surName = models.CharField(max_length=20, verbose_name="Soy Ad")
university = models.IntegerField(choices=UniversityList.objects.get(),default=0, verbose_name="Son Mezun Olunan Üniversite")
graduated = models.IntegerField(choices=graduatedList, default=0, verbose_name="Tahsil Durumu")
def __str__(self):
return f"{self.name},{self.surName}"
class Meta:
db_table = "mainland2"
verbose_name_plural = "Ar-Ge Personeller"

Doing objects.get() like that will try to get 1 object from the database, but you've got 5 by the sounds of it.
Something like objects.first() would work, but also it's a bad idea to perform that operation on the database in the model like that. You should override forms to perform the database operation in the creation of the form.
So, keep the field on the model simple;
university = models.IntegerField(default=0, verbose_name="Son Mezun Olunan Üniversite")
Then setup the choices in any forms which use the model. An example of this would look like;
class Mainland2Form(forms.ModelForm):
""" Mainland2 form """
class Meta:
""" Setup the form """
model = Mainland2
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
unis = UniversityList.objects.values_list(
'id', 'name'
)
# Make sure we've got an empty choice to avoid people not using the field taking the first choice
uni_choices = \
[(None, '')] + [(u[0], u[1]) for u in list(unis)]
self.fields['university'].choices = uni_choices
You can then override the admin form for your admin class. Check out these docs;
https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form
What you probably want though, given you're doing this in admin, is just a ForeignKey relationship. Django will then handle the choices for you.
And when it comes to FK relationships in admin, you should consider the additional overhead of having to select all the related objects in order to generate the select box.
If the related model has a lot of objects, you can reduce this database query by using raw_id_fields on your model admin.
For example;
class Mainland2Admin(admin.ModelAdmin):
raw_id_fields = ("university",)
https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.raw_id_fields

Related

Django admin list_display reverse to parent

I have 2 models:
1: KW (individual keywords)
2: Project (many keywords can belong to many different projects)
class KW(models.Model):
...
project = models.ManyToManyField('KWproject', blank=True)
class KWproject(models.Model):
ProjectKW = models.CharField('Main Keyword', max_length=1000)
author = models.ForeignKey(User, editable=False)
Now when user is in Admin for KWproject they should be able to see all keywords belonging to selected project in list_display. I achieved this but it doesn't feel like proper way.
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
list_display = ('Keywordd', 'author')
def Keywordd(self, obj):
return '%s' % (obj.id, obj.ProjectKW)
Keywordd.allow_tags = True
Keywordd.admin_order_field = 'ProjectKW'
Keywordd.short_description = 'ProjectKW'
Is there better way to link and then list_display all items that have reverse relationship to the model? (via "project" field in my example)
As per the Django Admin docs:
ManyToManyField fields aren’t supported, because that would entail
executing a separate SQL statement for each row in the table. If you
want to do this nonetheless, give your model a custom method, and add
that method’s name to list_display. (See below for more on custom
methods in list_display.)
So, you may opt to implement a custom model method like so:
# models.py
class KW(models.Model):
...
project = models.ManyToManyField('KWproject', blank=True)
class KWproject(models.Model):
ProjectKW = models.CharField('Main Keyword', max_length=1000)
author = models.ForeignKey(User, editable=False)
def all_keywords(self):
# Retrieve your keywords
# KW_set here is the default related name. You can set that in your model definitions.
keywords = self.KW_set.values_list('desired_fieldname', flat=True)
# Do some transformation here
desired_output = ','.join(keywords)
# Return value (in example, csv of keywords)
return desired_output
And then, add that model method to your list_display tuple in your ModelAdmin.
# admin.py
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
list_display = ('Keywordd', 'author', 'all_keywords')
def Keywordd(self, obj):
return '%s' % (obj.id, obj.ProjectKW)
Keywordd.allow_tags = True
Keywordd.admin_order_field = 'ProjectKW'
Keywordd.short_description = 'ProjectKW'
Do take note: This can potentially be a VERY EXPENSIVE operation. If you are showing 200 rows in the list, then a request to the page will execute 200 additional SQL queries.

Django - CheckboxSelectMultiple without "------" choice

How can I remove "------" from rendered choices?
I use in my model form:
widgets = {
'event_form': forms.CheckboxSelectMultiple(),
}
In model I have IntegerField with choices:
EVENT_FORM_CHOICES = (
(1, _(u'aaaa')),
(2, _(u'bbbb')),
(3, _(cccc')),
(4, _(u'dddd')),
(5, _(eeee'))
)
rendered choices contain --------- as first possible choice. How I can get rid of it?
EDIT:
The only working way i figured out is (in init method):
tmp_choices = self.fields['event_form'].choices
del tmp_choices[0]
self.fields['event_form'].choices = tmp_choices
but it's not very elegant way :)
Update
a similar example maybe useful:
country = ModelChoiceField(reference_class = Country, choices= country_choices,
required=True, empty_label=None, widget=forms.Select)
If you want a solution client side instead:
<script>
$("#selectBox option[value='-----']").remove();
</script>
Django is including the blank choice because the field doesn't have a default value.
If you set a default value in your model, then Django will not include the blank choice.
class MyModel(models.Model):
event_form = models.PositiveSmallIntegerField(choices=EVENT_FORM_CHOICES, default=1)
If you don't want to set a default value in your model, then you can explicitly declare the field and choices in the model form, or change the choices in the model form's __init__ method.
I ran into a similar problem but fixed it this way. First, download and install https://pypi.python.org/pypi/django-multiselectfield. If you don't know how to install, look here: django-multiselectfield can't install. Then, in models.py:
from multiselectfield import MultiSelectField
CHOICES_FOR_ITEM_WITH_CHOICES = (
("choice 1", "choice 1"),
("choice 2", "choice 2"),
("choice 3", "choice 3"),
)
class MyModel(models.Model):
item_with_choices = MultiSelectField(max_length=MAX_LENGTH, null=True, blank=True)
In admin.py:
from .forms import MyModelForm
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
list_display = ('item_with_choices',)
list_filter = ('item_with_choices',)
search_fields = ('item_with_choices',)
admin.site.register(MyModel, MyModelAdmin)
In forms.py (you can name this whatever you like):
from .models import MyModel
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields = (
'item_with_choices',
)
def clean(self):
# do something that validates your data
return self.cleaned_data
This builds off the answer here: Django Model MultipleChoice

Django Admin Drop down selections

I use the django admin for updating various data on the MySQL database. I use the basic django admin for this. When entering in new data, I would like to be able to have it so people can only select from a few options to enter in new text data.
For example:
The table holds colors, so instead of letting the admin person (data entry individual in our case) just enter in anything into the text box, how can I get the django admin to only give them several options to choose from?
This can be done via the model field argument choices
myfield = models.CharField(max_length=256, choices=[('green', 'green'), ('red', 'red')])
The only problem with this is that if you already have a value in the database that doesn't match one of these, django might just default it to one of the choices.
If that's a problem and you want to preserve those values, I might override the admin form and either only supply the ChoiceField on add operations or dynamically add whatever is in the DB as one of the valid choices.
class MyForm(ModelForm):
MY_CHOICES = [('green', 'green'), ('red', 'red')]
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
if self.instance.id:
CHOICES_INCLUDING_DB_VALUE = [(self.instance.field,)*2] + self.MY_CHOICES
self.fields['my_field'] = forms.ChoiceField(
choices=CHOICES_INCLUDING_DB_VALUE)
class MyAdmin(admin.ModelAdmin):
form = MyForm
It's better to define a "models.CharField" field type in your model
your_choices = (
('1', u'yello'),
('2', u'red'),
('3', u'black'),
)
types = models.CharField(max_length=32, choices=your_choices, null=True, blank=True)
Then in "admin.py" you should add this new field in your custom admin class like below.
class YourModelAdmin(admin.ModelAdmin):
fields = ('types', )
admin.site.register(YourModel, YourModelAdmin)

django manytomany validation

Please see the code below. Basically, when the user creates an object of this class, they need to specify the value_type. If value_type==2 (percentage), then percentage_calculated_on (which is a CheckboxSelectMultiple on the form/template side needs to have one or more items checked. The model validation isn't allowing me to validate like I'm trying to -- it basically throws an exception that tells me that the instance needs to have a primary key value before a many-to-many relationship can be used. But I need to first validate the object before saving it. I have tried this validation on the form (modelform) side (using the form's clean method), but the same thing happens there too.
How do I go about achieving this validation?
INHERENT_TYPE_CHOICES = ((1, 'Payable'), (2, 'Deductible'))
VALUE_TYPE_CHOICES = ((1, 'Amount'), (2, 'Percentage'))
class Payable(models.Model):
name = models.CharField()
short_name = models.CharField()
inherent_type = models.PositiveSmallIntegerField(choices=INHERENT_TYPE_CHOICES)
value = models.DecimalField(max_digits=12,decimal_places=2)
value_type = models.PositiveSmallIntegerField(choices=VALUE_TYPE_CHOICES)
percentage_calculated_on = models.ManyToManyField('self', symmetrical=False)
def clean(self):
from django.core.exceptions import ValidationError
if self.value_type == 2 and not self.percentage_calculated_on:
raise ValidationError("If this is a percentage, please specify on what payables/deductibles this percentage should be calculated on.")
I tested out your code in one of my projects' admin app. I was able to perform the validation you required by using a custom ModelForm. See below.
# forms.py
class MyPayableForm(forms.ModelForm):
class Meta:
model = Payable
def clean(self):
super(MyPayableForm, self).clean() # Thanks, #chefsmart
value_type = self.cleaned_data.get('value_type', None)
percentage_calculated_on = self.cleaned_data.get(
'percentage_calculated_on', None)
if value_type == 2 and not percentage_calculated_on:
message = "Please specify on what payables/deductibles ..."
raise forms.ValidationError(message)
return self.cleaned_data
# admin.py
class PayableAdmin(admin.ModelAdmin):
form = MyPayableForm
admin.site.register(Payable, PayableAdmin)
The Admin app uses the SelectMultiple widget (rather than CheckboxSelectMultiple as you do) to represent many to many relationships. I believe this shouldn't matter though.

Django Form with no required fields

I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)