Multi select input in admin site - django

I am trying to create a form in Admin site that uses two fields from two different tables (Employee, Product) as input (single select & multi-select) and make it available for admin user selection and write this to another table (JobQueue).
Following is my code.
models.py
class Employee(models.Model):
id = models.IntegerField(primary_key=True, verbose_name='Employee Code')
name = models.CharField(max_length=200, verbose_name='Employee Name')
def __str__(self):
return self.name
class Product(models.Model):
STATUS = (('New', 'New'), ('Go', 'Go'), ('Hold', 'Hold'), ('Stop', 'Stop'))
code = models.IntegerField(primary_key=True, max_length=3, verbose_name='Product Code')
name = models.CharField(max_length=100, verbose_name='Product Name')
def __str__(self):
return self.name
class JobQueue(models.Model):
emp_name = models.CharField(max_length=200, default='1001')
product_code = models.CharField(max_length=200, default='100')
admin.py:
class JobQueueAdmin(admin.ModelAdmin):
form = JobQueueForm
fieldsets = (
(None,{'fields': ('emp_name', 'product_code'),}),)
def save_model(self, request, obj, form, change):
super(JobQueueAdmin, self).save_model(request, obj, form, change)
forms.py:
class JobQueueForm(forms.ModelForm):
# Single select drop down
emp_name = forms.ModelChoiceField(queryset=Employee.objects.all(), widget=forms.ChoiceField())
# Multiselect checkbox
product_code = forms.MultiValueField(queryset=Product.objects.all(), widget=forms.CheckboxSelectMultiple(), required=False)
def save(self, commit=True):
return super(JobQueueForm, self).save(commit = commit)
class Meta:
model = JobQueue
fields = ('emp_name', 'product_code')
When I start the web-server, I get the following error:
AttributeError: 'ModelChoiceField' object has no attribute 'to_field_name'
Could someone please help me how do I let the user to pick the values from JobQueueForm and save the same in JobQueue table ?

Related

How to change a field of model prior saving the model based on another field in django 3.1

I need to be able to set the KeyIndex field of the Settings model to a value that is equal to
lastExtension - firstExtension
How can i do that
this is the content of my model
models.py
class Settings(models.Model):
KeyIndex = models.CharField(max_length=150, blank=True, name='Key_Index')
firstExtension = models.CharField(max_length=15, blank=False, null=False, default='1000')
lastExtension = models.CharField(max_length=15, blank=False, null=False, default='1010')
def save(self, *args, **kwargs):
f = int(self.firstExtension)
l = int(self.lastExtension)
a = [0] * (l - f)
self.KeyIndex = str(a)
return super(Settings, self).save()
class KeyFiles(models.Model):
setting = models.ForeignKey(Settings, on_delete=models.CASCADE)
keyFile = models.FileField(upload_to='key File', null=True, blank=True, storage=CleanFileNameStorage,
validators=[FileExtensionValidator(allowed_extensions=['bin']), ])
this is the content of my form
forms.py
class ShowAdminForm(forms.ModelForm):
class Meta:
model = Settings
fields = '__all__'
files = forms.FileField(
widget=forms.ClearableFileInput(attrs={"multiple": True}),
label=_("Add key Files"),
required=False,validators=[FileExtensionValidator(allowed_extensions=['bin'])]
)
def save_keyFile(self, setting):
file = KeyFiles(setting=setting, keyFile=upload)
file.save()
and t
admin.py
class KeyFilesAdmin(admin.TabularInline):
model = KeyFiles
#admin.register(IPPhone_Settings)
class IPPhoneSettingsAdmin(admin.ModelAdmin):
form = ShowAdminForm
inlines = [KeyFilesAdmin]
def save_related(self, request, form, formsets, change):
super(IPPhoneSettingsAdmin, self).save_related(request, form, formsets, change)

showing entries based on selection type in django

my question is i want to show only particular titles under music_track (musicmodel)field when type = track(title model) in my django admin site
class album(models.Model):
def get_autogenerated_code():
last_id = album.objects.values('id').order_by('id').last()
if not last_id:
return "AL-"+str(0)
return "AL-"+str(last_id['id'])
album_name = models.CharField( max_length=150, blank=False )
music_track = models.ManyToManyField("title")
def __str__(self):
return (self.album_name)
class Meta:
verbose_name = "Album"
verbose_name_plural = "Albums"
class title(models.Model):
def get_autogenerated_code():
last_id = title.objects.values('id').order_by('id').last()
if not last_id:
return "TT-"+str(0)
return "TT-"+str(last_id['id'])
upc_code = models.CharField(max_length=15, default="N/A", blank=False)
display_name = models.CharField(max_length=150, blank=False)
type = models.ForeignKey(Asset_Type, on_delete=models.CASCADE, null=True)
def __str__(self):
return (self.display_name+ " " +self.code)
admin.site.register( [album, title] )
From your question, I am understanding that while creating an album in your admin panel, you require that the music_track should only show the titles having type as track. My solution for this is:
In your admin.py file
from .models import title, album, Asset_type
class AlbumForm(forms.ModelForm):
class Meta:
model = Product
fields = ('album_name', 'music_track', )
def __init__(self, user, *args, **kwargs):
super(AlbumForm, self).__init__(*args, **kwargs)
type = Asset_type.objects.get(type='track')
self.fields['music_track'].queryset = Title.objects.filter(type=type)
class MyModelAdmin(admin.ModelAdmin):
form = AlbumForm
admin.site.register(album, MyModelAdmin)
Maybe this can give you the idea you need.

Valuerror in admin form

Following is my model and form:
models.py
class Employee(models.Model):
id = models.PositiveIntegerField(primary_key=True, verbose_name='Employee Code')
name = models.CharField(max_length=200, verbose_name='Employee Name')
def get_names(self):
return Employee.objects.values_list('name', 'name')
class JobQueue(models.Model):
emp_name = models.ForeignKey(Employee, on_delete=models.CASCADE)
product_code = models.ManyToManyField(Product)
forms.py
class JobQueueForm(forms.ModelForm):
emp = Employee()
prod = Product()
emp_name = forms.ChoiceField(choices = emp.get_names)
product_code = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=prod.get_products)
def save(self, commit=True):
return super(JobQueueForm, self).save(commit = commit)
class Meta:
model = JobQueue
fields = ('emp_name', 'product_code')
When I try to add new employee from JobQueue form, I get the following error:
ValueError at /admin/timesheet/jobqueue/add/
Cannot assign "'some_name'": "JobQueue.emp_name" must be a "Employee" instance.
Any idea what I am doing wrong here?
You have a name clash, basically, which is a red herring for you when debugging this.
You have emp_name in your form which is a ChoiceField (string) but when you save the form to the database the backing model JobQueue is expecting emp_name to be an instance of an employee.
In your case you'd need to override the save method in your form, which you've shown intent on doing already, and fetch the employee there overriding emp_name and saving it.
def save(self, commit=True):
self.emp_name = Employee.objects.get(name=self.cleaned_data['emp_name'])
return super(JobQueueForm, self).save(commit=commit)

'form.is_valid()' fails when I want to save form with choices based on two models

I want to let users to choose their countries. I have 2 models = Countries with some figures and CountriesTranslations. I am trying to make tuple with country (because user has FK to this model) and its translation. In front-end I see dropdown list of countries, but when I try to save the form, I see
error: Exception Value: Cannot assign "'AF'": "UserProfile.country" must be a "Countries" instance.
Error happens at the line if user_profile_form.is_valid():
# admindivisions.models
class Countries(models.Model):
osm_id = models.IntegerField(db_index=True, null=True)
status = models.IntegerField()
population = models.IntegerField(null=True)
iso3166_1 = models.CharField(max_length=2, blank=True)
iso3166_1_a2 = models.CharField(max_length=2, blank=True)
iso3166_1_a3 = models.CharField(max_length=3, blank=True)
class Meta:
db_table = 'admindivisions_countries'
verbose_name = 'Country'
verbose_name_plural = 'Countries'
class CountriesTranslations(models.Model):
common_name = models.CharField(max_length=81, blank=True, db_index=True)
formal_name = models.CharField(max_length=100, blank=True)
country = models.ForeignKey(Countries, on_delete=models.CASCADE, verbose_name='Details of Country')
lang_group = models.ForeignKey(LanguagesGroups, on_delete=models.CASCADE, verbose_name='Language of Country',
null=True)
class Meta:
db_table = 'admindivisions_countries_translations'
verbose_name = 'Country Translation'
verbose_name_plural = 'Countries Translations'
# profiles.forms
class UserProfileForm(forms.ModelForm):
# PREPARE CHOICES
country_choices = ()
lang_group = Languages.objects.get(iso_code='en').group
for country in Countries.objects.filter(status=1):
eng_name = country.countriestranslations_set.filter(lang_group=lang_group).first()
if eng_name:
country_choices += ((country, eng_name.common_name),)
country_choices = sorted(country_choices, key=lambda tup: tup[1])
country = forms.ChoiceField(choices=country_choices, required=False)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
# profiles.views
def profile_settings(request):
if request.method == 'POST':
user_profile_form = UserProfileForm(request.POST, instance=request.user)
if user_profile_form.is_valid():
user_profile_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_profile_form = UserProfileForm(instance=request.user)
return render(request, 'profiles/profiles_settings.html', {
'user_profile_form': user_profile_form,
})
As I understand, country from ((country, eng_name.common_name),) is converted to str. What is the right way to keep country instance in the form? or if I am doing it in the wrong way, what way is correct?
EDITED:
As a possible solution is to use ModelChoiceField with overriding label_from_instance as shown below:
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang='en', *args, **kwargs):
super(CountriesChoiceField, self).__init__(*args, **kwargs)
self.user_lang = user_lang
def label_from_instance(self, obj):
return obj.countriestranslations_set.get(lang_group=self.user_lang)
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(
queryset=Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name'),
widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
but this solution produces too much queries because of the query in label_from_instance and page loads too slowly. Would appreciate any advice.
You probably want to use forms.ModelChoiceField instead of the forms.ChoiceField for your dropdown list.
The ModelChoiceField builds based on a QuerySet and preserves the model instance.
Seems to be solved.
The version below produces 7 queries in 29.45ms vs 73 queries in 92.52ms in the EDITED above. I think it is possible to make it even faster if to set unique_together for some fields.
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang, *args, **kwargs):
queryset = Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name')
super(CountriesChoiceField, self).__init__(queryset, *args, **kwargs)
self.translations = OrderedDict()
for country in queryset:
name = country.countriestranslations_set.get(lang_group=user_lang).common_name
self.translations[country] = name
def label_from_instance(self, obj):
return self.translations[obj]
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
So, now it is possible to have choices based on two (2) models with a good speed. Also the DRY principle is applied, so if there is a need to use choices multiple times in different forms - no problem.

Django Form Validators on Comparing One Object Against Another

I am creating a Non Disclosure Agreement form that a user fills out after registering and logging in. I am using a custom signup form with AllAuth and pre-populating parts of the form. I pre-populate the first and last name into the top part of the form as shown below in first screen shot, but as a part of the digital signature I am setting up; I need to validate the typed signature field matches the name of the first_name and the last_name concatenated together per the second screen-shot. I know I need to setup a validator based on Django Form & Field Validations and I've tried several things but just can't get my mind rapped around it. Any help putting this together would be huge...thank you.
My Models
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile", verbose_name="user")
...
class NonDisclosure(Timestamp):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="nda", verbose_name="profile")
user_signature = models.CharField(max_length=250, verbose_name='Signature')
user_street = models.CharField(max_length=250, verbose_name='Street Address')
user_city = models.CharField(max_length=250, verbose_name='City')
user_state = models.CharField(max_length=2, verbose_name='State Initials')
user_zip = models.IntegerField(verbose_name='Zip Code')
phone = models.CharField(max_length=25, verbose_name='Phone Number')
cash_on_hand = models.CharField(max_length=250, verbose_name='Cash on Hand')
value_of_securities = models.CharField(max_length=250, verbose_name='Value of Securities')
equity_in_real_estate = models.CharField(max_length=250, verbose_name='Equity on Real Estate')
other = models.CharField(max_length=250, verbose_name='Other Assets')
#property
def username(self):
return self.profile.username
#property
def first_name(self):
return self.profile.first_name
#property
def last_name(self):
return self.profile.last_name
#property
def email(self):
return self.profile.email
class Meta:
verbose_name = 'Non Disclosure Agreement'
verbose_name_plural = 'Non Disclosure Agreements'
def __str__(self):
return "%s" % self.profile
def get_absolute_url(self):
return reverse('nda_detail', args=[str(self.id)])
My Views:
class NonDisclosureForm(BaseModelForm):
cash_on_hand = forms.CharField(required=False)
value_of_securities = forms.CharField(required=False)
equity_in_real_estate = forms.CharField(required=False)
other = forms.CharField(required=False)
class Meta:
model = NonDisclosure
fields = ['user_signature', 'user_street', 'user_city', 'user_state', 'user_zip', 'phone', 'cash_on_hand', 'value_of_securities', 'equity_in_real_estate', 'other']
class NdaCreate(CreateView):
form_class = NonDisclosureForm
template_name = 'nda/nda_form.html'
def form_valid(self, form):
form.instance.profile = Profile.objects.get(user=self.request.user)
form.instance.created_by = self.request.user
return super(NdaCreate, self).form_valid(form)
Firstly, you should subclass ModelForm, not BaseModelForm. Write a clean_<fieldname> method for your user_signature field, and make sure that the value is as expected. You can access self.instance.created_by to check.
class NonDisclosureForm(ModelForm):
...
class Meta:
model = NonDisclosure
fields = ['user_signature', ...]
def clean_user_signature(self):
user_signature = self.cleaned_data['user_signature']
expected_name = '%s %s' % (self.instance.created_by.first_name, self.instance.created_by.last_name)
if user_signature != expected_name:
raise forms.ValidationError('Signature does not match')
return user_signature
Then you need to update your view so that it sets instance.created_by. You can do this by overriding get_form_kwargs.
class NdaCreate(CreateView):
def get_form_kwargs(self):
kwargs = super(NdaCreate, self).get_form_kwargs()
kwargs['instance'] = NonDisclosure(created_by=self.request.user)
return kwargs