Django : Dropdown menu to show only unconfigured values - django

I have created two models -
class DID_Definition_Model(models.Model): # DID to Region-Carrier Mapping
region_carrier = models.ForeignKey(Telco_Carrier_Mapper_Model, on_delete=models.CASCADE)
did_number= models.CharField(max_length=32, validators=[alphanumeric], primary_key=True)
did_cost= models.DecimalField(max_digits=10, decimal_places=2)
created_on_date = models.DateTimeField(default=timezone.now)
class DID_Number_Assignment_Model(models.Model): #DID Number Assignment
did_selector = models.ForeignKey(DID_Definition_Model, on_delete=models.CASCADE, primary_key=True)
subscriber_department=models.CharField(max_length=200)
usage_assignment=models.ForeignKey(Usage_Assignment_Model, on_delete=models.CASCADE)
employee_email=models.EmailField()
employee_fullname=models.CharField(max_length=200)
created_on_date = models.DateTimeField(default=timezone.now)
And created the below view -
def did_assignment_form(request, did_selector=0):
if request.method =="GET":
if did_selector==0:
form = DID_Number_Assignment_Model_Form()
else:
did_assignment_item = DID_Number_Assignment_Model.objects.get(pk=did_selector)
form = DID_Number_Assignment_Model_Form(instance=did_assignment_item)
return render(request, 'MASTERHANDLER/did_assignment_form.html', {'form':form})
else:
if id==0:
context = DID_Number_Assignment_Model.objects.values('did_selector')
if did_selector not in context:
form = DID_Number_Assignment_Model_Form(request.POST)
else:
did_assignment_item = DID_Number_Assignment_Model.objects.get(pk=did_selector)
form = DID_Number_Assignment_Model_Form(request.POST, instance = did_assignment_item)
if form.is_valid():
form.save()
return redirect('did_assignment_list')
Form detail below -
class DID_Number_Assignment_Model_Form(forms.ModelForm):
class Meta:
model = DID_Number_Assignment_Model
fields = ('did_selector', 'usage_assignment', 'employee_fullname', 'employee_email', 'subscriber_department' )
labels = {
'did_selector' : 'DID Selector',
'usage_assignment' : 'Number Usage ',
'employee_fullname' : 'Employee Full Name',
'employee_email' : 'Employee Email',
'subscriber_department' : 'Employee Department',
}
def __init__(self, *args, **kwargs):
super(DID_Number_Assignment_Model_Form,self).__init__(*args, **kwargs)
# TO SET drop down default text for a field , optional -
self.fields['did_selector'].empty_label = "Select"
self.fields['usage_assignment'].empty_label = "Select"
# TO SET a field which may be optional -
self.fields['subscriber_department'].required = False
#self.fields['region_assigned'].required = False
This form works with no problems but with one little oddity. If I create an object in DID_Number_Assignment_Model_Form with 'did_selector' field as the foreign key, the very same value of 'did_selector' is shown for the next creation process.
My question is that how can I show only those did selector values which have not been configured. Sample screenshot below -

I was able to find an answer. What essentially I was looking for a method to over ride the queryset on a select field to exclude options already used.
I did this under forms.py by first creating a queryset and getting required values for a specific field.
Then assign these values as default using 'queryset' keyword on the field.
defs_with_no_assignments = DID_Definition_Model.objects.filter(did_number_assignment_model__isnull=True)
available_did= defs_with_no_assignments.values_list('did_number', flat=True)
self.fields['did_selector'].queryset = available_did

Related

How to predefine value inside model/form in Django?

I'm creating simple app which allows users to create group.
When user create group it has following fields:
name
desc
inviteKey - i would this field to be hidden and generate 10 characters code and then send it.
My models:
class Group(models.Model):
groupName = models.CharField(max_length=100)
description = models.CharField(max_length=255)
inviteKey = models.CharField(max_length=255)
class Members(models.Model):
userId = models.ForeignKey(User, on_delete=models.CASCADE)
groupId = models.ForeignKey(Group, on_delete=models.CASCADE)
isAdmin = models.BooleanField(default=False)
Form:
class GroupForm(forms.ModelForm):
groupName = forms.CharField(label='Nazwa grupy', max_length=100)
description = forms.CharField(label='Opis', max_length=255)
inviteKey: forms.CharField(label='Kod wstępu')
class Meta:
model = Group
fields = ['groupName', 'description', 'inviteKey' ]
View:
def createGroup(request):
if request.method == "POST":
form = GroupForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, f'Group created')
return redirect('/')
else:
inviteKey = generateInviteKey()
form = GroupForm(initial={'inviteKey': inviteKey})
return render(request, 'group/createGroup.html',{'form': form})
Now i have form and inviteKey is visible and editable. I want this key to be visible but not editable.
The best way to do that my opinion is to set the default value for your invitation key in your model, that way, token is created "in the background" with a unique key, but we can go further.
For example :
import uuid
token = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
)
This way you are sure that the token is unique (UUID is unique by design, but still) you cannot edit it so no wrong token can occur and last of all each object will get a unique token with no work on your side.
I am using UUID because it is recommended by Django as per the Documentation for token and unique identifier.
Note : If you set the UUID with a default value, you cannot get it before the object is created, depending on your use you might want to set it in the form (see answer below).
You can make the field disabled, so:
class GroupForm(forms.ModelForm):
groupName = forms.CharField(label='Nazwa grupy', max_length=100)
description = forms.CharField(label='Opis', max_length=255)
inviteKey = forms.CharField(label='Kod wstępu', disabled=True)
class Meta:
model = Group
fields = ['groupName', 'description', 'inviteKey' ]
This will also prevent a user from fabricating a POST request that contains a different invite key.
A problem that one now has to solve however is that we do not want to generate a different inviteKey when the user submits the form. This can be handled with session data, although it is not a very elegant solution. In that case we thus change the view to:
def createGroup(request):
if request.method == 'POST' and 'inviteKey' in request.session:
inviteKey = request.session['inviteKey']
form = GroupForm(request.POST, initial={'inviteKey': inviteKey})
if form.is_valid():
form.save()
messages.success(request, f'Group created')
return redirect('/')
else:
request.session['inviteKey'] = inviteKey = generateInviteKey()
form = GroupForm(initial={'inviteKey': inviteKey})
return render(request, 'group/createGroup.html',{'form': form})
You probably alo might want to make your inviteKey field unique, to prevent creating multiple groups with the same inviteKey:
class Group(models.Model):
groupName = models.CharField(max_length=100)
description = models.CharField(max_length=255)
inviteKey = models.CharField(max_length=255, unique=True)

What is difference between these two method of updating DB using form?

I'm trying update db using form. I want to select title in dropdown, and update 'opening_crawl' field to input from text area.
models.py :
class Movies(models.Model):
episode_nb = models.IntegerField(primary_key=True)
title = models.CharField(max_length=64, unique=True, null=False)
opening_crawl = models.TextField(null=True)
director = models.CharField(max_length=32)
producer = models.CharField(max_length=128)
release_date = models.DateField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True, editable=True)
forms.py:
class TitleDropDownForm(forms.Form):
title = forms.ModelChoiceField(queryset=Movies.objects.only('title'), empty_label=None)
opening_crawl = forms.CharField(widget=forms.Textarea)
views.py:
def update(request):
msg = ''
if request.method == 'POST':
form = TitleDropDownForm(request.POST)
if form.is_valid():
#method 1 : it updates 'opening_crawl' properly, but not 'updated_time'.
movie = form.cleaned_data['title']
movie.opening_crawl = form.cleaned_data['opening_crawl']
movie.save()
#method 2
#h = Movies.objects.get(pk=1)
#h.opening_crawl = 'HAND WRITTEN MESSAGE!'
#h.save()
return redirect(request.META.get('HTTP_REFERER'))
else:
form = TitleDropDownForm()
if not form.fields['title'].queryset:
msg = 'No data available.'
return render(request, 'ex07/update.html', context={'form' : form, 'msg' : msg})
method 1 works with 'opening_crawl' field, but 'updated' datetime field was not changed.
When I tried like method 2, it updates both fields properly.
What is the difference between two method? Is there any misunderstanding?
I suspect it's because you're using .only()
From the Django documentation:
When saving a model fetched through deferred model loading (only() or defer()) only the fields loaded from the DB will get updated. In effect there is an automatic update_fields in this case. If you assign or change any deferred field value, the field will be added to the updated fields.
In your second method, you're getting the entire model, without any deferred fields (i.e. updated)

Django ModelChoiceField Issue

I've got the following Situation, I have a rather large legacy model (which works nonetheless well) and need one of its fields as a distinct dropdown for one of my forms:
Legacy Table:
class SummaryView(models.Model):
...
Period = models.CharField(db_column='Period', max_length=10, blank=True, null=True)
...
def __str__(self):
return self.Period
class Meta:
managed = False # Created from a view. Don't remove.
db_table = 'MC_AUT_SummaryView'
Internal Model:
class BillCycle(models.Model):
...
Name = models.CharField(max_length=100, verbose_name='Name')
Period = models.CharField(max_length=10, null=True, blank=True)
Version = models.FloatField(verbose_name='Version', default=1.0)
Type = models.CharField(max_length=100, verbose_name='Type', choices=billcycle_type_choices)
Association = models.ForeignKey(BillCycleAssociation, on_delete=models.DO_NOTHING)
...
def __str__(self):
return self.Name
Since I don't want to connect them via a Foreign Key (as the SummaryView is not managed by Django) I tried a solution which I already used quite a few times. In my forms I create a ModelChoiceField which points to my Legacy Model:
class BillcycleModelForm(forms.ModelForm):
period_tmp = forms.ModelChoiceField(queryset=SummaryView.objects.values_list('Period', flat=True).distinct(),
required=False, label='Period')
....
class Meta:
model = BillCycle
fields = ['Name', 'Type', 'Association', 'period_tmp']
And in my view I try to over-write the Period Field from my internal Model with users form input:
def billcycle_create(request, template_name='XXX'):
form = BillcycleModelForm(request.POST or None)
data = request.POST.copy()
username = request.user
print("Data:")
print(data)
if form.is_valid():
initial_obj = form.save(commit=False)
initial_obj.ModifiedBy = username
initial_obj.Period = form.cleaned_data['period_tmp']
initial_obj.Status = 'Creating...'
print("initial object:")
print(initial_obj)
form.save()
....
So far so good:
Drop Down is rendered correctly
In my print Statement in the View ("data") I see that the desired infos are there:
'Type': ['Create/Delta'], 'Association': ['CP'], 'period_tmp': ['2019-12']
Still I get a Select a valid choice. That choice is not one of the available choices. Error in the forms. Any ideas??

How to update Django model data with a unique constraint using a ModelForm?

I have two models with one of them defined with a constraint (see "Entities" model below).
I built two forms, one to create new model data and another to update model data. Create form works properly but update form throws an error saying about of already existing items (my constraints is based on the unique combination of two fields). No matter what field I modify within the update form, same error is thrown.
For example, modyfing only "notes" field in an "entity" instance leads to the following error.
Entities with this Name and Company already exists.
How to properly implement my form (and/or models) so that constraint is preserved (an entity with the same name has to be unique within a company) and modification of a non constrained field don't throws an error?
models.py
class Entities(models.Model):
company = models.ForeignKey(Companies, on_delete=models.CASCADE)
name = models.CharField(max_length=50, blank=False, null=False)
notes = models.TextField(blank=True, null=True)
class Meta:
# Constraint here (entity name + company combination name must be unique)
constraints = [models.UniqueConstraint(fields=['name', 'company'], name='unique_company_entity')]
managed = True
db_table = 'entities'
def __str__(self):
object_name = self.name + " " + self.company.name
return object_name
class Companies(models.Model):
name = models.CharField(max_length=50, blank=False, null=False)
notes = models.CharField(max_length=50, blank=True, null=True)
class Meta:
managed = True
db_table = 'companies'
def __str__(self):
object_name = self.name
return object_name
views.py
def entity_edit(request,entity_id):
companies = Companies.objects.all().order_by('name')
entity_id = int(entity_id)
entity = Entities.objects.get(id = entity_id)
if request.method == 'POST':
form = EntityEditForm(request.POST,instance=entity)
if form.is_valid():
post_result = form.save(commit=True)
redirect_url_valid = "/contacts/companies/entities/" + str(entity.id) + "/view/"
return redirect(redirect_url_valid)
else:
form = EntityEditForm(instance=entity)
return render(request,'entity_edit_form.html',{
'companies': companies,
'entity': entity,
'form': form
})
forms.py
class EntityEditForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.label_suffix = ''
self.fields['name'] = forms.CharField(label='Name',widget=forms.TextInput(attrs={ 'class': 'form-control' }))
self.fields['company'] = forms.ModelChoiceField(queryset=Companies.objects.all(),label='Company',required=True,widget=forms.Select(attrs={ 'class': 'form-control' }))
self.fields['notes'] = forms.CharField(label='Notes',required=False,widget=forms.Textarea(attrs={ 'class': 'form-control' }))
class Meta(object):
model = Entities
fields = ('name','company','notes')
# Méthodes de nettoyage des champs du formulaire
def clean_name(self):
name = self.cleaned_data['name']
return name
def clean_company(self):
company = self.cleaned_data['company']
return company
def clean_notes(self):
notes = self.cleaned_data['notes']
return notes
When you save a ModelForm that is initialised with an instance, e.g. MyForm(request.POST, instance=instance_to_update), Django will exclude the instance instance_to_update from its query to check for any uniqueness constraints. See the code.
The code you show in your question is correct, but since you're getting the error, there can only be two explanations:
Either you forgot to pass the instance to your ModelForm's initialiser (form = EntityEditForm(request.POST)
Or you're initialising it with the wrong instance and modifying it to duplicate another already existing instance.

Validation fails on a select box whose contents are added by an Ajax call

This question is related to this one
Remove all the elements in a foreign key select field
I had a foreign key field in my model which was getting pre-populated by its data and I wanted the select list to be empty. I did achieve that but the validation fails when I submit the form.
The error says "Select a valid choice option. 1 is not one of the available choices).
These are my models
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe)
ingredient = models.ForeignKey(Ingredient)
serving_size = models.ForeignKey(ServingSize)
quantity = models.IntegerField()
order = models.IntegerField()
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
class RecipeIngredientForm(forms.ModelForm):
serving_size = forms.ChoiceField(widget=forms.Select())
class Meta:
serving_size = forms.ChoiceField(widget=forms.Select())
model = RecipeIngredient
fields = ('ingredient', 'quantity', 'serving_size')
widgets = {
'ingredient': forms.TextInput(attrs={'class' : 'recipe_ingredient'}),
'quantity': forms.TextInput(),
'serving_size' : forms.Select(attrs={'class' : 'ddl'}),
}
I get an error on the third line
recipeIngredients = models.RecipeIngredientFormSet(request.POST)
print(recipeIngredients.errors)
objRecipeIngredients = recipeIngredients.save(commit=False)
I want the select box to be empty because it gets populated by an ajax call. Any ideas what to do so the model passes the validation?
EDIT
Serving Size Model
class ServingSize(models.Model):
name = models.CharField(max_length = 255)
unit = models.CharField(max_length = 125)
food_group = models.ForeignKey(FoodGroup)
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
objects = models.Manager()
dal_objects = ServingSizeManager()
def __unicode__(self):
return self.name;
First, why do you have serving_size in the Meta class?
I would use an extra field in the ModelForm and leave out serving_size field altogether.
class RecipeIngredientForm(forms.ModelForm):
mycustomfield = forms.ChoiceField(widget=forms.Select())
class Meta:
model = RecipeIngredient
exclude = ('serving_size', 'created', 'updated') #etc
Then in the view I would manipulate the form to assign a valid ServingSize to the serving_size field.
[EDIT]
Alright, your actual implementation will depend on what you are pulling through ajax and how. But see the following code: -
Your form: -
class CustomRecipeIngredientForm(forms.ModelForm):
recipe = forms.ModelChoiceField( Recipe.objects.all(),
widget=forms.Select(attrs={'class':'customclass',}))
ingredient = forms.ModelChoiceField( Ingredient.objects.all(),
widget=forms.Select(attrs={'class':'recipe_ingredient',}))
my_custom_serving_size_field = forms.ChoiceField(widget=forms.Select(attrs={'class':'ddl',}))
quantity = forms.IntegerField()
order = forms.IntegerField()
class Meta:
model = RecipeIngredient
exclude = ('serving_size', 'created', 'updated',)
Pull your data through ajax into the my_custom_serving_size_field
Your view: -
def my_view(request):
if request.method == 'POST':
form = CustomRecipeIngredientForm(data=request.POST)
if form.is_valid():
new_recipe_ingredient = form.save(commit=False)
new_recipe_ingredient.serving_size = ServingSize.objects.get(pk=form.cleaned_data['my_custom_serving_size_field'])
new_recipe_ingredient.save()
return HttpResponseRedirect(reverse('redirect_to_wherever'))
else:
form = CustomRecipeIngredientForm()
return render_to_response('path/to/my_template.html', {'form': form}, context_instance=RequestContext(request))
Of course, your ServingSize.objects.get() logic will depend on what your are pulling through ajax and how. Try something along these lines and let us know.
Looks like you want a ModelChoiceField, which
Allows the selection of a single model
object, suitable for representing a
foreign key