Pass choices from views to form - python

I use the form Bannerform to create new Banner object trough the add_banner views (and template). I use the class Options to definite which affiliation objects to permit in the form Bannerform (affiliation field).
I'm trying to pass choices from the views to the form but it give me ValueError: too many values to unpack (expected 2)
My code worked when new_affiliation was a ForeignKey but now I need more values. I think I must definite 'choices' in the views or I will have problem on the first migration (it appear to call the database tables from the models.py but not from the views.py, so if I put Options.objects.get(id=1) on the models.py it give error because the tables don't exist yet).
My form.py:
from django import forms
from core.models import Options, Banner, Affiliation #somethings other
class BannerForm(forms.ModelForm):
name = forms.CharField(max_length=32)
affiliation = forms.ChoiceField('choices')
#affiliation = forms.ModelChoiceField('choices') #same error
class Meta:
model = Banner
exclude = (#some fields)
My models.py:
from django.db import models
from django.contrib.auth.models import User
from django import forms
class Options(models.Model):
new_affiliation = models.ManyToManyField('Affiliation')
#new_affiliation = models.ForeignKey('Affiliation') #this worked (with some changes in views)
class Affiliation(models.Model):
name = models.CharField(max_length=32, unique=True)
class Banner(models.Model):
name = models.CharField(max_length=32, unique=True)
affiliation = models.ForeignKey(Affiliation)
My views.py:
def add_banner(request):
if request.method == 'POST':
#some code here
else:
options = Options.objects.get(id=1)
print(options.new_affiliation.all()) #controll
choices = options.new_affiliation.all()
print(choices) #controll
form = BannerForm(choices, initial={
#some code regarding other fields
})
return render(request, 'core/add_banner.html', {'form': form})
My add_banner.html:
<form role="form" id="banner_form" enctype="multipart/form-data "method="post" action="../add_banner/">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
{{ field.errors }}
{{ field.label }}
{{ field }}
{{ field.help_text }}
<br />
{% endfor %}
Any help will be apreciated.
Updated. I changed only views.py:
def add_banner(request):
if request.method == 'POST':
#some code here
else:
options = Options.objects.get(id=1)
print(options.new_affiliation.all()) #controll
choices = tuple(options.new_affiliation.all())
print(choices) #controll
form = BannerForm(choices, initial={
#some code regarding other fields
})
return render(request, 'core/add_banner.html', {'form': form})
But still give error.
Update 2. If I pass choices directly from form.py it works:
My views.py:
def add_banner(request):
if request.method == 'POST':
#some code here
else:
form = BannerForm(request.POST or None, initial={
#some code regarding other fields
})
return render(request, 'core/add_banner.html', {'form': form})
My forms.py:
class BannerForm(forms.ModelForm):
options = Options.objects.get(id=1)
choices = options.new_affiliation.all()
name = forms.CharField(max_length=32)
affiliation = forms.ModelChoiceField(choices)
Unluckly this give problems on the first migration (see above).
I'm trying to pass choices using some init method...
My forms.py:
class BannerForm(forms.ModelForm):
name = forms.CharField(max_length=32)
affiliation = forms.ModelChoiceField(choices)
def __init__(self, *args, **kwargs):
options = Options.objects.get(id=1)
choices = options.new_affiliation.all()
#choices = kwargs.pop('choices')
super(RegentForm, self).__init__(*args, **kwargs)
self.fields['affiliation'] = choices
but it say that choices is not definite

From what I can see here, it looks like you are getting an error "too many values to unpack" because you are not sending "choices" as the correct type. A ChoiceField takes choices only as a tuple, as seen in the documentation for models. If you are looking to define choices based on a QuerySet, you'll have to convert it into a tuple which can be interpreted as valid "choices". In one of my projects for example, I needed to prepare a set of years as a tuple so that I could allow users to select from a list of pre-determined years. I specified the following function to do this:
def years():
response = []
now = datetime.utcnow()
for i in range(1900, now.year + 1):
response.append([i, str(i)])
return tuple(response)
Since tuples are meant to be immutable, it usually isn't a good idea to cast them, just based on principle. However, in this case it seems necessary as a measure to declare that you are okay with the possible variability with these statements.
In your specific situation, you might consider doing something like this:
choices = tuple(options.new_affiliation.all().values())
I have not tested this code, and I frankly am not completely familiar with your project and may be mistaken in some part of this response. As a result, it may require further tweaking, but give it a try. Based on your error, this is definitely where the program is breaking currently. Update here if you make any progress.

Done!
My form.py:
class BannerForm(forms.ModelForm):
name = forms.CharField(max_length=32, label='Nome')
def __init__(self, *args, **kwargs):
options = Options.objects.get(id=1)
choices = options.new_affiliation.all()
super(BannerForm, self).__init__(*args, **kwargs)
self.fields['affiliation'] = forms.ModelChoiceField(choices)
self.fields['affiliation'].initial = choices
class Meta:
model = Banner
My views.py:
def add_banner(request):
if request.method == 'POST':
#some code here
else:
form = BannerForm(request.POST or None, initial={
#some code here
})
return render(request, 'core/add_banner.html', {'form': form})
Thank you

Related

Django Form Dynamic Fields looping over each field from POST and creating records

I'm looking for some advice where to go from here. I've been working on making a Form, which dynamically generates its fields.
The form is working and generating everything correctly. However, I am having issues with how to save the actual form data. I'm looking for each field to save as a new item in a model.
The View Class from view.py
class MaintenanceCheckListForm(LoginRequiredMixin, FormView):
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
form_class = MaintenanceCheckListForm
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
form.cleaned_data
for key, values in form:
MaintenanceCheckList.objects.create(
item = key,
is_compliant = values
)
return super().form_valid(form)
The Form from forms.py
class MaintenanceCheckListForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MaintenanceCheckListForm, self).__init__(*args, **kwargs)
items = Maintenance_Item.objects.all()
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
for item in items:
self.fields[str(item.name)] = forms.ChoiceField(
label=item.name,
choices=CHOICES,
widget=forms.RadioSelect,
initial='F',
)
The Model, from models.py
class MaintenanceCheckList(CommonInfo):
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
id = models.AutoField(primary_key=True)
item = models.CharField(max_length=100)
is_compliant = models.CharField(max_length=20, choices= CHOICES)
I am having trouble accessing the data from the Form when it POST's. I've done some troubleshooting where I have set the values statically in the '''form_valid''' and it appears to generate the correct amounts of entires in the model. However the trouble begins when I attempt to insert the values from the POST.
I receieve the below error, which I believe it is trying to dump all the keys and values into a single item instead of looping over each key, value and creating the item.
DataError at /maintenance/checklist
value too long for type character varying(100)
Request Method: POST
Request URL: http://t1.localhost:8000/maintenance/checklist
Django Version: 3.1.6
Exception Type: DataError
Exception Value:
value too long for type character varying(100)
I'm fairly new to the world of Django (4 weeks and counting so far, and maybe 12 weeks into python). So any assistance would be amazing!
I believe you have somewhat gone on a tangent. There's a simpler solution of using Model formsets for what you want.
First if you want a custom form make that:
from django import forms
class MaintenanceCheckListComplianceForm(forms.ModelForm):
item = forms.CharField(widget = forms.HiddenInput())
is_compliant = forms.ChoiceField(
choices=MaintenanceCheckList.CHOICES,
widget=forms.RadioSelect,
initial='F',
)
class Meta:
model = MaintenanceCheckList
fields = ('item', 'is_compliant')
Next use it along with modelformset_factory in your views:
from django.forms import modelformset_factory
class MaintenanceCheckListFormView(LoginRequiredMixin, FormView): # Changed view name was a bit misleading
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
instances = form.save()
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['queryset'] = MaintenanceCheckList.objects.none()
kwargs['initial'] = [{'item': obj['name'], 'is_compliant': 'F'} for obj in Maintenance_Item.objects.all().values('name')]
return kwargs
def get_form(self, form_class=None):
kwargs = self.get_form_kwargs()
extra = len(kwargs['initial'])
form_class = modelformset_factory(MaintenanceCheckList, form=MaintenanceCheckListComplianceForm, extra=extra)
return form_class(**kwargs)
Now in your template:
<form method="post">
{{ form }}
</form>
Or manually render it:
<form method="post">
{{ form.management_form }}
{% for sub_form in form %}
Item: {{ sub_form.item.value }}
{{ sub_form }}
{% endfor %}
</form>
Note: The above usage is a bit weird due to the naming of the formset variable as form by the FormView you should look into improving that a bit.
Note: Looking at the implementation it feels a bit weird to do this. I would advice you to redesign your models a bit. Perhaps a foreign key between your models? It basically feels like you have duplicate data with this implementation.

Issue with Django form POST method

I am having a problem with the following view and form. The form loads correctly however when I edit any of the fields it does not save. After a bit of debugging I think it is due to one of two things: either request.method == "POST" is evaluating to false, or form.is_valid() is evaluating to false. So potentially something wrong with my template or my clean() method? I've searched previous questions and can't find anything that helps. I've also checked my clean() method against the Django docs and think it is OK.
views.py
#login_required
def edit_transaction(request, pk):
transaction = get_object_or_404(Transaction, pk=pk)
if request.method == "POST":
form = TransactionForm(request.POST, instance=transaction)
if form.is_valid():
transaction = form.save(commit=False)
transaction.updated = timezone.now()
transaction.save()
return redirect('view_transaction_detail', pk=transaction.pk)
else:
form = TransactionForm(request=request, instance=transaction)
return render(request, 'budget/new_transaction.html', {'form': form})
forms.py
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = ('title', 'transaction_type', 'category', 'budgeted_amount', 'actual_amount', 'date', 'comments',)
#new_category field to allow you to add a new category
new_category = forms.CharField(max_length=30, required=False, label="New Category Title")
def __init__(self, request, *args, **kwargs):
super(TransactionForm, self).__init__(*args, **kwargs)
#category is now not a required field because you will use category OR new_category
self.fields['category'].required=False
#set to allow use of self.request.user to set user for category
self.request = request
def clean(self):
category = self.cleaned_data.get('category')
new_category = self.cleaned_data.get('new_category')
if not category and not new_category:
# raise an error if neither a category is selected nor a new category is entered
raise forms.ValidationError('Category or New category field is required')
elif not category:
# create category from new_category
category, created = Category.objects.get_or_create(title=new_category, defaults={'user': self.request.user})
self.cleaned_data['category'] = category
return super(TransactionForm, self).clean()
template
{% extends 'budget/base.html' %}
{% block content %}
<h2>New transaction</h2>
<h4>To add a new category, leave Category blank and enter your new category in the New Category Title field</h4>
<form method="POST" class="post-form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
{% endblock %}
update following answer - accessing request through kwargs
def __init__(self, *args, **kwargs):
super(TransactionForm, self).__init__(*args, **kwargs)
self.fields['category'].required=False
self.request = kwargs.pop('request', None)
As I mentioned on your last question, since you've changed the signature of the form's init method you need to pass the request both times you instantiate it. You're only doing so when it is not POST; so, when it is a POST, Python takes the data that you passing and assigns it to the request argument, leaving the data itself blank.
form = TransactionForm(request, data=request.POST, instance=transaction)
Note it is precisely for this reason that is is a bad idea to change the signature; instead, pass request as a keyword argument and inside the method get it from kwargs.

how to to display the category user created by the current user on every page

as title indicates I'm trying to send the name of a category user created by the current user on every page. My initial attempt was simply
{% if user.is_authenticated == category.author %}
{{category.name}}
{% endif %}
but this only displays the category in a certain page, while I want to display this in the navbar which I have it included for the every page. So I thought I should do category = models.foreignkey('category') in my user model but got told I should set a queryset in a template context processor. which I'm not sure it's the best way to do.
Can someone please direct me how I should do such matter?
here's my code
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
description = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL)
and in my views.py
#login_required
def add_category(request):
if not request.user.is_superuser and Category.objects.filter(author=request.user).exists():
return render(request,'main/category_already_exists.html')
if request.method == 'POST':
category = Category(author=request.user)
form = CategoryForm(request.POST, request.FILES, instance=category)
if form.is_valid():
form.save(commit=True)
return redirect('category', category_name_url=category.name)
else:
form = CategoryForm()
context = {
"form":form
}
return render(request, 'main/add_category.html',context)
and this is my simplified category view
def category(request, category_name_url):
category_name = decode_url(category_name_url)
category = Category.objects.get(name=category_name)
context = {
"category":category,
}
return render(request, "main/category.html", context)
and this is my model for my user
from userena.models import UserenaBaseProfile
class MyProfile(UserenaBaseProfile):
user = models.OneToOneField(User, unique=True, verbose_name=_('user'), related_name='my_profile')
Perhaps you should write your own context processor and include it in settings. Link to docs https://docs.djangoproject.com/en/dev/ref/templates/api/#context-processors
Definition from Django docs:
"Context processors are functions that receive the current HttpRequest as an argument and return a dict of data to be added to the rendering context.
Their main use is to add common data shared by all templates to the context without repeating code in every view."
You can add this information in request.session. You can do this like this:
Suppose your login view is this:
def login(request):
# Do login stuff
if user.is_active():
request.session['categories'] = [ c.name for c in Category.objects.filter(author=request.user)] # add to the session
return redirect('/somepage')
Display this data in every page like this:
{% for c in request.session.categories %}
{{ c }}
{% endfor %}
And update the category list every time the a new category is added like this:
#login_required
def add_category(request):
if not request.user.is_superuser and Category.objects.filter(author=request.user).exists():
return render(request,'main/category_already_exists.html')
if request.method == 'POST':
category = Category(author=request.user)
form = CategoryForm(request.POST, request.FILES, instance=category)
if form.is_valid():
form.save(commit=True)
request.session['categories'] = [ c.name for c in Category.objects.filter(author=request.user)]
return redirect('category', category_name_url=category.name)
else:
form = CategoryForm()
context = {
"form":form
}
return render(request, 'main/add_category.html',context)

Django - Establishing Many To Many Relationship Between 2 Models Without Through Table Using Formsets

I have a model Attribute and Product, declared like this:
class Attribute(models.Model):
value = models.TextField()
owner = models.ForeignKey(User)
type = models.ForeignKey(AttributeType)
image = ImageField(upload_to='attributes', null=True, blank=True)
related_attribute = models.ManyToManyField('self', blank = True, null = True)
class BaseWorkspace(models.Model):
name = models.CharField(max_length=255)
owner = models.ForeignKey(User)
attributes = models.ManyToManyField('Attribute', blank = True, null = True)
created = CreationDateTimeField()
modified = ModificationDateTimeField()
comments = models.ManyToManyField('Comment', blank = True, null = True )
sort_order = models.IntegerField(blank = True)
class Product(BaseWorkspace):
project = models.ForeignKey('Project', related_name='products')
how can I establish m-m relationship using formsets? I have tried model formset factories like this:
AttributeFormset = modelformset_factory(Attribute, form=AttributeForm)
with this function in the generic view:
def form_valid(self, form):
f = form.instance
f.sort_order = Product.default_sort_order()
f.owner = self.request.user
f.project = get_object_or_404(Project, pk=self.kwargs['pk'])
context = self.get_context_data()
attribute_form = context['attribute_form']
if attribute_form.is_valid():
self.object = form.save()
attribute_form.instance = self.object
attribute_form.save()
return HttpResponseRedirect(reverse(self.get_success_url()))
else:
return self.render_to_response(self.get_context_data(form=form))
but I cannot get it to work. any ideas?
Try something like this:
from django.forms.models import modelformset_factory
def my_view_function(request) :
# not sure where the product whose formset we are working on comes from
product = <whatever>
AttributeFormSet = modelformset_factory(Attribute)
if request.method == "POST" :
# POST bound formset
formset = AttributeFormSet(request.POST, queryset=Attribute.objects.filter(product=product))
# If the entire formset is valid
if formset.is_valid() :
for form in formset:
# Save each form in the set
b = form.save()
else :
#There was an error (add a message using the messages framework?)
pass
else :
# initial formset w/o post
formset = AttributeFormSet(queryset=Attribute.objects.filter(product=product))
...
Its kind of hard to give you more specific answer, I think we would need the entire view function or view class if you are using class based views.
In your template, something as simple as this (from the docs) should do it.
<form method="post" action="">
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
</form>
If you need the ability to add forms to the formset at runtime w/ javascript look at this: http://code.google.com/p/django-dynamic-formset/. Ive never used it, but at the very least it looks like a step in the correct direction.
EDIT
First exclude product from the formset
AttributeFormSet = modelformset_factory(Attribute, exclude=('product',))
then change the form processing block to not commit on save, and manually attach the product.
if formset.is_valid() :
for form in formset:
# get this form's instance
b = form.save(commit=False)
# attach product
b.product = product
# save the instance
b.save()
By using f = form.instance you access the original instance. If the attribute_form is valid you call the save method on the form, instead of the f. All the changes you did on f will be lost.
Have a look at saving-objects-in-the-formset how to update instances of a formset before saving them.

Auto fill ForeignKey for Django Model

So I'm building a basic Q&A site-- Each topic has a series of questions associated with it, and each question has multiple answers associated with it.
I'm creating the user input for questions and they have to associated with a topic. This is the questions model
#models.py
class Question(models.Model):
movie = models.ForeignKey(Movie, blank=True, null=True)
question_text = models.CharField(max_length = 1000)
question_detail = models.CharField(max_length = 5000, blank = True, null = True)
q_pub_date = models.DateTimeField(auto_now_add = True)
q_author = models.ForeignKey(User)
class QuestionForm(ModelForm):
def save(self, user = None, force_insert = False, force_update = False, commit = True):
q = super(QuestionForm, self).save(commit = False)
q.q_author = user
if commit:
q.save()
return q
class Meta:
model = Question
exclude = ('movie', 'q_author', 'q_pub_date')
This is the URL conf
#urls.py
url(r'^(?P<movie_id>\d+)/add_question/$', 'add_question'),
Now here is the view
#views.py
def add_question(request, movie_id):
if request.method == "POST":
form = QuestionForm(request.POST, request.FILES)
#QuestionForm.movie = Movie.objects.get(pk = movie_id)
if form.is_valid():
form.save(user = request.user)
return HttpResponseRedirect("/home/")
else:
form = QuestionForm()
return render_to_response("qanda/add_question.html", {'form': form}, context_instance = RequestContext(request))
This is the HTML code
#add_question.html
<h1> Add Question: {{ user.username }}</h1>
<form action = "" method = "post">{% csrf_token %}
{{ form.as_p }}
<input type = "submit" value = "Ask" />
<input type = "hidden" name = "next" value = "{{ next|escape }}" />
</form>
In the view, the commented out line is what I added to the view to try and auto save the model. When adding a question, the URL has the ID of the movie it is associated with, and my thought is to take that ID and then plug it into the ForeignKey to identify which movie is associated with the question. However, when I use my code, it changes all of the Questions' movie associations to the current movie instead of just changing that specific question's movie association. Without the code, it doesn't associate a Movie with the Question at all. How do I fix this?
Use this:
#views.py
def add_question(request, movie_id):
if request.method == "POST":
form = QuestionForm(request.POST, request.FILES)
if form.is_valid():
question = form.save(user = request.user)
question.movie = Movie.objects.get(pk = movie_id)
question.save()
return HttpResponseRedirect("/home/")
else:
form = QuestionForm()
return render_to_response("qanda/add_question.html", {'form': form}, context_instance = RequestContext(request)
For question asked in comment
You should avoid using absolute URLs in views or templates. Consider a scenario, where you decide to change home URL from /home/ to /myhome/. You will have to edit it where ever you have used them. It is always better to name the urls (docs):
# URL Conf
url(r'^home/$', 'home_view', name="home_url"),
url(r'^(?P<movie_id>\d+)/add_question/$', 'add_question', name="add_question_url"),
url(r'^home/(?P<movie_id>\d+)/$', 'movie_view', name="movie_url"),
The name argument act as an unique identifier to your actual URLs
Now in you views:
from django.core.urlresolvers import reverse
def some_view(request):
...
return HttpResponseRedirect(reverse('home_url'))
Now what ever change you make to the URL (say /home/ to /myhome/ makes no effect to the view as long as the name argument has the same value in the URL conf.
If you wish to pass parameters (like movie_id in your case)
def some_view(request, movie_id):
...
return HttpResponseRedirect(reverse('movie_url', kwargs={'movie_id':movie_id}))
The same concept should be used in templates to avoid hard-coding URLS in templates. Please read this for more details

Categories