Django ModelChoiceField Issue - django

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??

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)

Django ModelForm: change a field value when blank

Is there any way to change a field value (related to a foreign key) in a Django ModelForm once the form is initialized and filled by the user (I'm using request.POST). I want to change the value when the user doesn't select any option of the dropdown list. I tried this formulari_mostra.data['pools'] = 1 in views.py after saving the feedback from the form with no result:
def sample_form(request):
formulari_mostra=FormulariMostra()
if request.method=="POST":
formulari_mostra=FormulariMostra(request.POST or None)
if formulari_mostra.is_valid():
feedback = formulari_mostra.save(commit=False)
sample = Sample.objects.all()
feedback.sample = sample
feedback.save()
formulari_mostra.save_m2m()
formulari_mostra.data['pools'] = 1
messages.success(request, 'Mostra enregistrada correctament!')
return render(request, "sample/formulari_mostra.html", {'formulari':formulari_mostra})
I got this message:
This QueryDict instance is immutable
I know I can set an initial (default) before introducing data in the form but I don't want to have the default option highlighted in the dropdown.
My model:
class Sample(models.Model):
id_sample = models.AutoField(primary_key=True)
name = models.CharField(unique=True, max_length=20)
sample_id_sex = models.ForeignKey(Sex, on_delete=models.CASCADE, db_column='id_sex', verbose_name='Sexe')
indexes = models.ManyToManyField(Index, through='SamplePoolIndexCand', through_fields=('sample_id', 'index_id'), blank=True, verbose_name="Índexs")
pools = models.ManyToManyField(Pool, through='SamplePoolIndexCand', through_fields=('sample_id', 'pool_id'), blank=True, verbose_name="Pools")
gene_cand_lists = models.ManyToManyField(GeneCandList, through='SamplePoolIndexCand', through_fields=('sample_id', 'gene_cand_list_id'), blank=True, verbose_name="Llista de gens candidats")
class Meta:
db_table = 'sample'
def __str__(self):
return self.name
My forms.py:
class FormulariMostra(ModelForm):
class Meta:
model = Sample
fields = ("name", "sample_id_sex", "pools",)
first - you are setting the polls property after save() is called, so even if this would work, you are not saving the change.
second - if you want to set the polls property to model, then set it to the model instead of the form (formulari_mostra). I dont know how your models look like, so I can only assume the model which has the pools property is in the variable feedback, so you want to do:
feedback = formulari_mostra.save(commit=False)
feedback.pools = 1
feedback.save()

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 modelformset_factory() adding field manually in views

Disclaimer: I am new at Django so I'm sure my code is ugly.
Problem:
My current Model is built as follows:
class person(models.Model):
email = models.EmailField()
date = models.DateField()
phone_number = models.IntegerField()
name = models.CharField(max_length = 50)
belongs_to_group = models.ForeignKey(Group, related_name='group', on_delete=models.SET_NULL, null=True)
belongs_to_user = models.ForeignKey(User, related_name='user', on_delete=models.SET_NULL, null=True)
I have built a modelformset_factory for this using the following code:
personModelFormset = modelformset_factory(
person,
fields=('email', 'date' , 'phone_number', 'name'),
extra=1)
This makes the fields the form renders in the HTML email, date , phone_number, and name. This means that to successfully save the form to the database, I need to also add the fields belongs_to_group and belongs_to_user manually since the website user shouldn't be able to edit these (they should be automatically generated).
Attempted Solution:
To try to do this, I used the following view:
def classes(request):
#add form creation method here
user = request.user
group = value #taken from another form
if request.method == 'POST':
form_2 = personModelFormset (request.POST)
if form_2.is_valid():
for form in form_2:
form.belongs_to_group = value
form.belongs_to_user = user
form.save()
return redirect('home')
But this does not append the information to the form. This method works for me in a normal modelform, so I think I'm not using the modelformset_factory correctly. Does anyone know how I should correctly append the "behind the scenes" model fields to the formsetfactory? Thank you!

Limit values in the modelformset field

I've been trying to solve this problem for a couple of days now, getting quite desperate. See the commented out code snippets for some of the things I've tried but didn't work.
Problem: How can I limit the values in the category field of the IngredientForm to only those belonging to the currently logged in user?
views.py
#login_required
def apphome(request):
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
# Attempt #1 (not working; error: 'IngredientFormFormSet' object has no attribute 'fields')
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.none())
# ingrformset.fields['category'].queryset = Category.objects.filter(user=request.user)
# Attempt #2 (doesn't work)
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.filter(category__user_id = request.user.id))
models.py:
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
class Ingredient(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(Category, null=True, blank=True)
counter = models.IntegerField(default=0)
forms.py:
class IngredientForm(ModelForm):
class Meta:
model = Ingredient
fields = ('name', 'category')
UPDATE: I've made some progress but the solution is currently hard-coded and not really usable:
I found out I can control the categoryform field via form class and then pass the form in the view like this:
#forms.py
class IngredientForm(ModelForm):
category = forms.ModelChoiceField(queryset = Category.objects.filter(user_id = 1))
class Meta:
model = Ingredient
fields = ('name', 'category')
#views.py
IngrFormSet = modelformset_factory(Ingredient, form = IngredientForm, extra=1, fields=('name', 'category'))
The above produces the result I need but obviously the user is hardcoded. I need it to be dynamic (i.e. current user). I tried some solutions for accessing the request.user in forms.py but those didn't work.
Any ideas how to move forward?
You don't need any kind of custom forms. You can change the queryset of category field as:
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
IngrFormSet.form.base_fields['category'].queryset = Category.objects.filter(user__id=request.user.id)
Category.objects.filter(user=request.user)
returns a list object for the initial value in your form which makes little sense.
Try instead
Category.objects.get(user=request.user)
or
Category.objects.filter(user=request.user)[0]