I'm having a problem getting my view to update a manytomany field. It returns this after the form is submitted.
Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/home/footbook/Ubuntu One/webapps/fb/poc/../poc/activity/views.py" in activity_save_page
44. group_names = form.cleaned_data['groups'].split()
Exception Type: AttributeError at /activity_save/
Exception Value: 'QuerySet' object has no attribute 'split'
Here are the files.
Models.py
class Group (models.Model):
group_nm = models.CharField(max_length=64)
group_desc = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
active_yn = models.BooleanField(default=True)
def __unicode__(self):
return self.group_nm
class Activity(models.Model):
activity_nm = models.CharField(max_length=60)
activity_desc = models.CharField(max_length=250)
startdt = models.DateField()
enddt = models.DateField()
crdt = models.DateTimeField(auto_now_add=True,editable=False)
groups = models.ManyToManyField(Group)
upddt = models.DateTimeField(editable=False)
def save(self, *args, **kwargs):
if not self.id:
self.crdt = datetime.date.today()
self.upddt = datetime.datetime.today()
super(Activity, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
forms.py
def make_custom_datefield(f):
formfield = f.formfield()
if isinstance(f, models.DateField):
formfield.widget.format = '%m/%d/%Y'
formfield.widget.attrs.update({'class':'datePicker', 'readonly':'true'})
return formfield
class ActivitySaveForm(forms.ModelForm):
formfield_callback = make_custom_datefield
def __init__(self, *args, **kwargs):
super(ActivitySaveForm, self).__init__(*args, **kwargs)
self.fields['activity_nm'].label = "Activity Name"
self.fields['activity_desc'].label = "Describe It"
self.fields['startdt'].label = "Start Date"
self.fields['enddt'].label = "End Date"
self.fields['groups'].label ="Group"
class Meta:
model = Activity
views.py
def activity_save_page(request):
if request.method == 'POST':
form = ActivitySaveForm(request.POST)
if form.is_valid():
act, created = Activity.objects.get_or_create(
activity_nm = form.cleaned_data['activity_nm']
)
act.activity_desc = form.cleaned_data['activity_desc']
if not created:
act.group_set.clear()
group_names = form.cleaned_data['groups'].split()
for group_name in group_names:
group, dummy = Group.objects.get_or_create(group_nm=group_name)
act.group_set.add(group)
act.save()
return HttpResponseRedirect('/activity/')
else:
form = ActivitySaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('activity_save.html', variables)
I think it would work if I wasn't using the modelform, but I need it to implement this datepicker. Since it's a manytomany field, I want to split them when they are entered into the database, but my queryset fails. I've tried changing this a bunch of different ways, but I'm stuck. I've seen a lot of similar questions, but they either had foreign keys or no modelform.
Thanks.
EDIT:
activity_save.html
{% extends "base.html" %}
{% block title %}Save Activity{% endblock %}
{% block head %}Save Activty{% endblock %}
<input class="datePicker" readonly="true" type="text" id="id_startdt" />
<input class="datePicker" readonly="true" type="text" id="id_enddt" />
{% block content %}
<form action="{% url activity.views.activity_save_page act_id%}" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="save it" />
</form>
{% endblock %}
Exactly as the error describes: a QuerySet does not have a split method. You cannot call my_qs.split().
form.cleaned_data['groups'] returns cleaned data; it has already taken care of the form string-to-python-object conversion for you, which in the case of a ManyToManyField is ultimately represented by a QuerySet in python.
A date field returns date objects, IntegerField an integer, CharFields a string, etc. in the same way via form cleaning.
If you want a list of group_names, you'd need to explicitly iterate through the objects in the QuerySet and pull their group_nm attribute.
group_names = [x.group_nm for x in form.cleaned_data['groups']]
I'm not sure you need to do all that in your view. You can directly save the form in the view without manually creating the objects and manipulating them.
Also, you need to get the id of activity so that you can update existing activity instance.
Update the urls.py to have these urls to have act_id:
url(r'^activity_app/save/(?P<act_id>\d+)/$', 'activity_app.views.activity_save_page'),
url(r'^activity_app/save/$', 'activity_app.views.activity_save_page'),
I would change the view to:
def activity_save_page(request, act_id=None):
act_inst = None
try:
if act_id:
act_inst = Activity.objects.get(id=act_id)
except Exception:
pass
if request.method == 'POST':
form = ActivitySaveForm(request.POST, instance=act_inst)
if form.is_valid():
return HttpResponseRedirect('/activity/')
else:
form = ActivitySaveForm(instance=act_inst)
variables = RequestContext(request, {
'form': form
})
return render_to_response('activity_save.html', variables)
Related
Im trying to add a field called, interested_fields inside my personalInfo model which users can choose from and the choices themselves come from another models' objects with the help of ManyToMany relation between the two models. Here are my models.py codes(I simplified my personal model by removing some other fields like name, age, etc in order to make it more readable for you):
class Field(models.Model):
id = models.AutoField(primary_key=True)
slug = models.CharField(max_length=16, default='default')
title = CharField(max_length=32)
class PersonalInfo(models.Model):
id = models.AutoField(primary_key=True)
interested_fields = models.ManyToManyField(Field, blank=True)
then, I created a ModelForm like this:
class InterestedFieldsForm(forms.ModelForm):
interested_fields = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=Field.objects.all(), required=False)
class Meta:
model = PersonalInfo
fields = ['interested_fields']
and created a get and post functions inside my views like this:
class PersonalView(View):
template_name = 'reg/personal.html'
def get(self, request, *args, **kwargs):
context = {}
context['fields'] = Field.objects.all()
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
user = request.user
if request.method == 'POST':
form = InterestedFieldsForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
else:
form = InterestedFieldsForm()
return render(request, 'reg/done.html', context={'form': form})
and finally in template, inside the form I added this for loop:
{% for field in fields %}
<label class="containerq ant-col ant-col-md-6 ant-col-xs-8" >
<span>
<input type="checkbox" name="interested_fields" {% if field.slug in user.personalInfo.interested_fields %} checked="checked" {% endif %} value="{{field.title}}">
<span style="margin-left:7px" class="checkmark"></span>
</span>
<span>{{field.title}}</span>
</label>
{% endfor %}
when I submit the form it gives me this error:
cannot unpack non-iterable Field object
Im new to django so I really dont know what am I doing wrong. thank you for your answers
You should use a ModelMultipleChoiceField
interested_fields = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Field.objects.all(), required=False).
I'm facing an issue. My goal is to know if a user already exist in DB. If it's true add it to the channel and create it.
There is only two fields in the form for consumer.
title of channel
name of seller (check if it's valid and if so add it)
I don't really why that does not work.
class CreateChannelView(CreateView):
model = Channel
form_class = CreateChannelForm
template_name = 'channel_new.html'
def form_valid(self, form):
form.instance.consumer = self.request.user
sellers = self.request.POST.get("seller")
current_seller = User.objects.filter(username=sellers)
if current_seller.count()<1:
raise forms.ValidationError('Username does not exist')
else:
existed_seller = User.objects.get(username=sellers)
form.instance.seller.add(existed_seller)
form.save()
return super(CreateChannelView, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('channel:channel_home')
class Channel(models.Model):
consumer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="channel_consumer", blank=True, null=True)
name = models.CharField(max_length=10)
seller = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="channel_seller")
def save(self, *args, **kwargs):
if not self.slug:
self.slug = unique_slugify(self, slugify(self.name))
super().save(*args, **kwargs)
def __str__(self):
return self.name
Here is my template with the form. I'm using classical field form and I add one field called seller which is related to my channel model
<form method="post" class="user-log">
{% csrf_token %}
{% for field in form %}
<div class="mt-10">
{{ field }}
</div>
{% endfor %}
<div class="mt-10">
<input type="text" name="seller" placeholder="Mike" onfocus="this.placeholder = ''" onblur="this.placeholder = 'Jean'" required="" class="single-input">
</div>
<input type="submit" class="" value=" Add a new channel">
</form>
ERROR:
RelatedObjectDoesNotExist at /channel/new/
Exception Value: Channel has no seller.
Trace:
form.instance.seller.add(existed_seller)
__class__
<class 'channel.views.CreateChannelView'>
current_seller
<QuerySet [<User: fredo>]>
existed_seller
<User: fredo>
form
<CreateChannelForm bound=True, valid=True, fields=(name)>
self
<channel.views.CreateChannelView object at 0x2b00798d3350>
sellers
'fredo'
Here is the answer :)
def form_valid(self, form):
form.instance.consumer = self.request.user
sellers = self.request.POST.get("seller")
current_seller = User.objects.filter(username=sellers)
if current_seller.count()<1:
raise forms.ValidationError('Username does not exist')
else:
existed_seller = User.objects.get(username=sellers)
form.instance.seller = existed_seller #here is the chnage
form.save()
return super(CreateChannelView, self).form_valid(form)
I am trying to create a form to submit a blog post on an author detail page, so that the blog post will automatically use the current author as its "blog_author" foreign key. I'm aware that this approach isn't "secure" - it's a project site, and I'm trying to learn a new design pattern.
The Django docs recommended using 1 parent view and 2 subviews to handle get and post respectively (https://docs.djangoproject.com/en/3.0/topics/class-based-views/mixins/).
The page renders fine with the get, but the post gives me an error reading "Page not found (404) - no blog post found matching the query." The exception is raised by my parent view (blog.views.AuthorDetail), but there is no traceback.
Edit: Form should have been a ModelForm from the beginning
Here are my views:
class BlogAuthorDetailView(generic.DetailView):
model = BlogAuthor
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = BlogSubmitForm()
return context
class BlogSubmit(SingleObjectMixin, FormView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogPost
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
#Should I be overriding form_valid() to use the line above? Not sure if I'm doing my data
#handling in the right place
return super().post(request, *args, **kwargs)
def form_valid(self, form):
blogpost = form.save(commit=False)
blogpost.blog_author = self.object
blogpost.save()
return redirect('blog_author-detail', pk=self.object.id)
class AuthorDetail(View):
def get(self, request, *args, **kwargs):
view = BlogAuthorDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = BlogSubmit.as_view()
return view(request, *args, **kwargs)
URLs:
urlpatterns = [
path('', views.index, name='index'),
path('blogs/', views.BlogPostListView.as_view(), name='blogs'),
path('blog/<int:pk>', views.BlogPostDetailView.as_view(), name='blogpost-detail'),
path('bloggers/', views.BlogAuthorListView.as_view(), name='bloggers'),
path('blogger/<int:pk>', views.AuthorDetail.as_view(), name='blog_author-detail'),
path('blog/<int:pk>/create', views.BlogCommentCreate.as_view(), name='comment_create')
]
the template:
{% extends "base_generic.html" %}
{% block content %}
<h1>Title: {{ blogauthor.title }}</h1>
<p><strong>Author:</strong> {{ blogauthor }}</p>
<p><strong>Biography:</strong> {{ blogauthor.biography }}</p>
<p><strong>User:</strong> {{ blogauthor.user }}</p>
<p><strong>Posts:</strong>
{% for blog in blogauthor.blogpost_set.all %}
<p> {{ blog.title }} </p>
{% endfor %} </p>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
<div style="margin-left:20px;margin-top:20px">
<h4>Comments: Coming Soon!</h4>
{% endblock %}
Model:
class BlogPost(models.Model):
date_created = models.DateField(blank=False, default = date.today)
blog_author = models.ForeignKey('BlogAuthor', on_delete = models.SET_NULL, null=True)
title = models.TextField(max_length=70)
content = models.TextField(max_length=400, null=False)
class Meta:
ordering = ['date_created']
def get_absolute_url(self):
"""Returns the url to access a particular blog post instance."""
return reverse('blogpost-detail', args=[str(self.id)])
def __str__(self):
return self.title
And the forms.py:
class BlogSubmitForm(forms.Form):
title = forms.CharField()
content = forms.CharField(widget=forms.Textarea(attrs={'cols': 40, 'rows': 8}))
date_created = forms.DateField()
At this point, I suspect that the problem is related to my redirect() call in the form_valid override.
The things I have tried include:
Changing the form’s action from blank to the same URL as in my URL paths (possible I did this wrong)
Changing the code in form_valid() to read form.instance.blog_author = self.object (same exact error message, so I don’t think it’s this)
Fiddling with the form_valid()’s redirect call, including: using self.object instead or a URL, using a hardcoded url, getting rid of the second argument, and changing the 2nd arg to pk=, slug=.
Adding a get_success_url override (don’t really know why this would work)
edit: one of the excepted post calls that showed up in my local server went to blog/blogger/4, which is the url I want. Not sure what the issue is.
This is confusing on how you are using the template. Anyway, I think the simplest solution here is to get the BlogAuthor data from request.user and that is most logical, otherwise, anyone can post anything from another user as long as they can predict their primary key(which is a security hole). Here is how you can try:
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogSubmit(LoginRequiredMixin, CreateView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogPost
def get_success_url(self):
return reverse('blog_author-detail', pk=self.object.id)
def form_valid(self, form):
form.blog_author = self.request.user.blogauthor # assuming BlogAuthor has OneToOne relation with User
return super(BlogSubmit, self).form_valid(form)
Update
Purpose of FormView is to collect data from Forms, where CreateView is to store and create a new instance. Anyway, you need to change your code like this to make it work:
class BlogSubmit(LoginRequiredMixin, SingleObjectMixin, FormView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogAuthor
def get_success_url(self):
return reverse('blog_author-detail', pk=self.object.id)
def form_valid(self, form):
self.object = self.get_object()
form.blog_author = self.object
form.save()
return super(BlogSubmit, self).form_valid(form)
Also update the form:
class BlogSubmitForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['title', 'date_created', 'content']
FYI, to make SingleObjectMixin work, you need to change the model from BlogPost to BlogAuthor
I have a formset like so:
class TransactionForm(ModelForm):
def __init__(self, *args, **kwargs):
try:
user = kwargs.pop("user")
except KeyError:
user = None
super(TransactionForm, self).__init__(*args, **kwargs)
self.fields["date"].widget.attrs["class"] = "datepicker"
if user is not None:
self.fields["categories"].queryset = Category.objects.get_all(user)
self.fields["account"].queryset = Account.objects.for_user(user)
class Meta:
model = Transaction
exclude = [""]
TransactionFormSet = modelformset_factory(Transaction, form=TransactionForm, exclude=("",))
View:
def transaction_create_view(request, account_id=None):
if request.method == "POST":
formset = TransactionFormSet(request.POST)
print(formset.errors)
if formset.is_valid():
for form in formset:
if form.is_valid and form.has_changed():
form.save()
if account_id is not None:
transactions = TransactionFormSet(queryset=Transaction.objects.for_account(account_id, request.user))
else:
transactions = TransactionFormSet(queryset=Transaction.objects.for_user(request.user))
transactions.form = curry(TransactionForm, user=request.user)
transactions.forms.insert(0, transactions.forms[-1])
del transactions.forms[-1]
context = {"transactions":transactions,}
return render(request, "transactions/transactions.html", context)
Model:
class Transaction(models.Model):
account = models.ForeignKey(Account)
date = models.DateField()
payee = models.CharField(max_length = 100)
categories = models.ManyToManyField(Category)
comment = models.CharField(max_length = 1000)
outflow = models.DecimalField(max_digits=10, decimal_places=3)
inflow = models.DecimalField(max_digits=10, decimal_places=3)
cleared = models.BooleanField()
class Category(models.Model):
title = models.CharField(max_length = 100)
subcategory = models.ForeignKey("self", blank=True, null=True)
user = models.ForeignKey(User)
budget = models.DecimalField(max_digits=10, decimal_places=2)
And template:
<form action="{% url 'transaction_create' %}" method="post">
{% csrf_token %}
{{ transactions.management_form }}
{% for form in transactions %}
{{ form.as_p }}
<input type="submit" value="Save transaction" />
<hr>
{% endfor %}
</form>
I input information in the empty form it produces and click on the save button, but the formset is not valid and I get this when I print the errors:
[{'categories': ['This field is required.']}, {}]
The categories in the template are represented by a <select>, where I select a category (the background is colored). Apparently it is not set though and thus I can't save, why is that? How can I fix it?
EDIT:
I have no idea why but now it works.
I reset the database re-created some data to see if that fixes things, and it did, I didn't change anything in the code.
If you override the save() in TransactionForm, you have to call save_m2m() to save the m2m objects.
https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/
How do I pass a parameter to my form?
someView()..
form = StylesForm(data_dict) # I also want to pass in site_id here.
class StylesForm(forms.Form):
# I want access to site_id here
You should define the __init__ method of your form, like that:
class StylesForm(forms.Form):
def __init__(self,*args,**kwargs):
self.site_id = kwargs.pop('site_id')
super(StylesForm,self).__init__(*args,**kwargs)
of course you cannot access self.site_id until the object has been created, so the line:
height = forms.CharField(widget=forms.TextInput(attrs={'size':site_id}))
makes no sense. You have to add the attribute to the widget after the form has been created. Try something like this:
class StylesForm(forms.Form):
def __init__(self,*args,**kwargs):
self.site_id = kwargs.pop('site_id')
super(StylesForm,self).__init__(*args,**kwargs)
self.fields['height'].widget = forms.TextInput(attrs={'size':site_id})
height = forms.CharField()
(not tested)
This is what worked for me. I was trying to make a custom form . This field in the model is a charfield but I wanted a choice field generated dynamically .
The Form:
class AddRatingForRound(forms.ModelForm):
def __init__(self, round_list, *args, **kwargs):
super(AddRatingForRound, self).__init__(*args, **kwargs)
self.fields['name'] = forms.ChoiceField(choices=tuple([(name, name) for name in round_list]))
class Meta:
model = models.RatingSheet
fields = ('name', )
The Views:
interview = Interview.objects.get(pk=interview_pk)
all_rounds = interview.round_set.order_by('created_at')
all_round_names = [rnd.name for rnd in all_rounds]
form = forms.AddRatingForRound(all_round_names)
return render(request, 'add_rating.html', {'form': form, 'interview': interview, 'rounds': all_rounds})
The Template:
<form method="post">
{% csrf_token %}
{% if interview %}
{{ interview }}
{% if rounds %}
{{ form.as_p }}
<input type="submit" value="Submit" />
{% else %}
<h3>No rounds found</h3>
{% endif %}
</form>
someView()..
form = StylesForm( 1, request.POST)
in forms.py
class StylesForm(forms.Form):
#overwrite __init__
def __init__(self,site_id,*args,**kwargs):
# call standard __init__
super().__init__(*args,**kwargs)
#extend __init__
self.fields['height'] =forms.CharField(widget=forms.TextInput(
attrs= {'size':site_id}))
height = forms.CharField()
or
someView()..
form = StylesForm(site_id = 1)
in forms.py
class StylesForm(forms.Form):
#overwrite __init__
def __init__(self,site_id):
# call standard __init__
super().__init__()
#extend __init__
self.fields['height'] =forms.CharField(widget=forms.TextInput(
attrs= {'size':site_id}))
height = forms.CharField()