Initial value for dropdown menu - django

I'd like to set an initial value on my dropdown form of "Select an Industry". Once the user selects a valid value from the dropdown AND saves the form, ideally, this option wouldn't be visible anymore within the list if the user were to go back to the form. If there is no way to do this, that's fine.
Models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone = models.CharField(blank=True, null=True, max_length=100)
industry = models.IntegerField(default=0)
Forms.py
class EditUserProfileForm (forms.ModelForm):
industry = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(EditUserProfileForm, self).__init__(*args, **kwargs)
self.fields['industry'].choices = [(t.industry, t.industryname) for t in Industry.objects.all()]
class Meta:
model = UserProfile
fields = (
'phone',
'industry',
)
Is it possible to set a default value without creating an instance of the Industry object whose industryname is "Select and Industry"?
Thanks!

If industry is an integer representing entries in a separate table, then it is a foreign key. Make it an actual ForeignKey field; then Django will automatically output a select box for that related model in your form.

Related

Django model form with field not existing in model

I am using Django 3.2
I have a model like this:
class BannedUser(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="ban_info")
reason = models.PositiveSmallIntegerField(choices=BANN_REASON_CHOICES)
banned_at = models.DateTimeField(auto_now_add=True)
expiry_date = models.DateField(null=True, blank=True, help_text=_('Date on which ban expires'))
I want to create a form that instead of asking user to select a date, simply asks the user to select the Ban duration. The form will then calculate the expiration_date in the clean() method.
BAN_DURATION_3_DAYS=3
# ...
BAN_DURATION_CHOICES = (
(BAN_DURATION_3_DAYS, _('3 Days')),
# ...
)
class BannedUserForm(forms.ModelForm):
class Meta:
model = BannedUser
fields = ['reason', 'ban_till']
The form field ban_till is a PositiveInteger that maps to the number of days. The intention is then to calculate the expiry_date from today by offsetting the integer amount.
I suppose one way would be to:
create a dynamic field ban_till
add field expiry_date to the form field list (but somehow prevent it from being rendered)
in the form's clean() method calculate the expiry_date and update that field
How to create a form to display field that does not exist in Model?
My solution:
class BannedUserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['ban_till'] = forms.IntegerField(widget=forms.ChoiceField())
super().__init__(*args, **kwargs)
class Meta:
model = BannedUser
fields = ['reason']
Is this the correct way to do this - or are there any gotchas I need to be aware of?

Exclude instance when updating Recursive Foreign Key in Django

I have the following model and modelform for an Employee:
models.py
class Employee(models.Model):
reports_to = models.ForeignKey(
'self', on_delete=models.SET_NULL,
null=True, blank=True)
forms.py
class EmployeeForm(forms.ModelForm):
class Meta:
model = Employee
The idea is that the boss of an Employee is themselves an Employee.
The problem is that, when I'm updating the instance, the respective form field created is a dropdown with all Employees, including the object I'm updating itself.
Is there an easy way to remove the instance itself from the dropdown options so that no employee has him/herself as their own boss?
PS.: I'm not looking for a solution that validates the form field after submitting a form, but rather removing the option from the form dropdown altogether. Thanks!
Yes, you can modify the queryset of the respective field, and omit the instance, if that instance (already) exists. Like:
class EmployeeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = self.instance
if instance.pk is not None:
self.fields['reports_to'].queryset = Employee.objects.exclude(pk=instance.pk)
class Meta:
model = Employee
In case the instance has a pk that is not None (that means that you edit the instance, not create a new one), then we thus "patch" the queryset that contains all the Employees, except for that one.

Django Admin, modify/customize the names in the select box of a manytomany field

I have 2 models Category and Product. Category has a FK to itself, and Product a FK to Companies.
class Product(Meta):
categories = models.ManyToManyField(Category, related_name='products')
class Category(SEO, MetaData):
parent = models.ForeignKey('self', blank=True, null=True, verbose_name='parent category', on_delete=models.CASCADE)
In Django Admin in Product Create/Edit Page I need to select the Categories for Parent.
At this moment is just a long select box, with the names of Categories. I want to introduce the all path of the category-subcategory.
Ex:
Now: Category A Name
Need: Category Parent Name :: SubCategory Parent Name :: Category A Name
I don't want to modify def __str__ because is used also in other places.
You could use a custom ModelForm like this:
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__' # It's actually best to list the fields out as per Two Scoops of Django
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['categories'].label_from_instance = \
lambda obj: obj.enumerated_path
Replace enumerated_path with whatever the Product model's property/method is named. Don't forget to use the form in your ModelAdmin by adding form = ProductForm.

M2M using through and form with multiple checkboxes

I'd like to create a form allowing me to assign services to supplier from these models. There is no M2M relationship defined since I use a DB used by others program, so it seems not possible to change it. I might be wrong with that too.
class Service(models.Model):
name = models.CharField(max_length=30L, blank=True)
class ServiceUser(models.Model):
service = models.ForeignKey(Service, null=False, blank=False)
contact = models.ForeignKey(Contact, null=False, blank=False)
class SupplierPrice(models.Model):
service_user = models.ForeignKey('ServiceUser')
price_type = models.IntegerField(choices=PRICE_TYPES)
price = models.DecimalField(max_digits=10, decimal_places=4)
I've created this form:
class SupplierServiceForm(ModelForm):
class Meta:
services = ModelMultipleChoiceField(queryset=Service.objects.all())
model = ServiceUser
widgets = {
'service': CheckboxSelectMultiple(),
'contact': HiddenInput(),
}
Here is the view I started to work on without any success:
class SupplierServiceUpdateView(FormActionMixin, TemplateView):
def get_context_data(self, **kwargs):
supplier = Contact.objects.get(pk=self.kwargs.get('pk'))
service_user = ServiceUser.objects.filter(contact=supplier)
form = SupplierServiceForm(instance=service_user)
return {'form': form}
I have the feeling that something is wrong in the way I'm trying to do it. I have a correct form displayed but it is not instantiated with the contact and checkboxes aren't checked even if a supplier has already some entries in service_user.
You are defining services inside your Meta class. Put it outside, right after the beginning of SupplierServiceForm. At the very least it should show up then.
Edit:
I misunderstood your objective. It seems you want to show a multiple select for a field that can only have 1 value. Your service field will not be able to store the multiple services.
So, by definition, your ServiceUser can have only one Service.
If you don't want to modify the database because of other apps using it, you can create another field with a many to many relationship to Service. That could cause conflicts with other parts of your apps using the old field, but without modifying the relationship i don't see another way.
The solution to my problem was indeed to redefine my models in oder to integrate the m2m relationship that was missing, using the through argument. Then I had to adapt a form with a special init method to have all selected services displayed in checkboxes, and a special save() method to save the form using m2m relationship.
class Supplier(Contact):
services = models.ManyToManyField('Service', through='SupplierPrice')
class Service(models.Model):
name = models.CharField(max_length=30L, blank=True)
class ServiceUser(models.Model):
service = models.ForeignKey(Service, null=False, blank=False)
supplier = models.ForeignKey(Supplier, null=False, blank=False)
price = models.Decimal(max_digits=10, decimal_places=2, default=0)
And the form, adapted from the very famous post about toppings and pizza stuff.
class SupplierServiceForm(ModelForm):
class Meta:
model = Supplier
fields = ('services',)
widgets = {
'services': CheckboxSelectMultiple(),
'contact_ptr_id': HiddenInput(),
}
services = ModelMultipleChoiceField(queryset=Service.objects.all(), required=False)
def __init__(self, *args, **kwargs):
# Here kwargs should contain an instance of Supplier
if 'instance' in kwargs:
# We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist.
initial = kwargs.setdefault('initial', {})
# The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data (checked boxes).
initial['services'] = [s.pk for s in kwargs['instance'].services.all()]
ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
supplier = ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
def save_m2m():
new_services = self.cleaned_data['services']
old_services = supplier.services.all()
for service in old_services:
if service not in new_services:
service.delete()
for service in new_services:
if service not in old_services:
SupplierPrice.objects.create(supplier=supplier, service=service)
self.save_m2m = save_m2m
# Do we need to save all changes now?
if commit:
self.save_m2m()
return supplier
This changed my first models and will make a mess in my old DB but at least it works.

How to hide model field in Django Admin?

I generate field automaticly, so I want to hide it from user. I've tried editable = False and hide it from exclude = ('field',). All this things hide this field from me, but made it empty so I've got error: null value in column "date" violates not-null constraint.
models.py:
class Message(models.Model):
title = models.CharField(max_length = 100)
text = models.TextField()
date = models.DateTimeField(editable=False)
user = models.ForeignKey(User, null = True, blank = True)
main_category = models.ForeignKey(MainCategory)
sub_category = models.ForeignKey(SubCategory)
groups = models.ManyToManyField(Group)`
admin.py:
class MessageAdminForm(forms.ModelForm):
def __init__(self, *arg, **kwargs):
super(MessageAdminForm, self).__init__(*arg, **kwargs)
self.initial['date'] = datetime.now()
class MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
list_display = ('title','user',)
list_filter = ('date',)
Based on your model setup, I think the easiest thing to do would change your date field to:
date = models.DateTimeField(auto_now=True)
that should accomplish what you're after and you don't even need to exclude it from the admin, it's excluded by default. If you have auto_now=True it will act as a 'last update time'. If you have auto_now_add=True it will act as a creation time stamp.
There are several other ways you could accomplish your goal if your use case is more complex than a simple auto date field.
Override the model's save method to put the value in.
class Message(models.Model):
title=models.CharField(max_length=100)
date = models.DateTimeField(editable=False)
def save(*args, **kwargs):
self.date = datetime.datetime.now()
super(Message, self).save(*args, **kwargs)
What you are trying to do with the Model Admin isn't quite working because by default django only transfers the form fields back to a model instance if the fields are included. I think this might be so the model form doesn't try to assign arbitrary attributes to the model. The correct way to accomplish this would be to set the value on the instance in your form's save method.
class MessageAdminForm(forms.ModelForm):
def save(*args, **kwargs):
self.instance.date = datetime.now()
return super(MessageAdminForm, self).save(*args, **kwargs)