2

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 %}
Community
  • 1
  • 1
jabs
  • 1,694
  • 4
  • 19
  • 36

2 Answers2

2

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']]
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • Thanks for helping to clarify querysets. But, I'm still at a loss at how to clear and save the m2m group queryset with the activity, once I retrieve with the `for` loop. Thanks for your help. – jabs Aug 27 '12 at 17:39
  • Clear and save the m2m? Sounds like a new question - you may want to re-ask it and be clear what you are looking for. To "Clear" an m2m, aka remove all associations, you'd call `my_m2m_field.clear()`. To add them, `my_m2m_field.add(*[1, 2, 3, 4])` – Yuji 'Tomita' Tomita Aug 27 '12 at 17:54
  • Thanks Yuji. If I can't get it worked out, I'll repost with a clearer question. +1 for the info. – jabs Aug 27 '12 at 22:01
  • Not a problem, let me know if there's anything else! – Yuji 'Tomita' Tomita Aug 27 '12 at 22:02
  • Solved it by using your approach. Thanks again! – jabs Aug 28 '12 at 19:41
2

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)
Rohan
  • 52,392
  • 12
  • 90
  • 87
  • Thanks for the detailed response. I get a different error with this approach though: `local variable 'act_inst' referenced before assignment`. The error references the form creation when it's not a POST. Why, when the form is initially created, would I want to have the form expect the variable to be set, since no data has been posted? Again, thanks! – jabs Aug 27 '12 at 16:44
  • @jabs oh yes, when `act_id` is `None` `act_inst` will not get defined. Please check updated answer. – Rohan Aug 27 '12 at 16:51
  • This time, I got a `Caught NoReverseMatch` error. Here is my call in the template: `
    {% csrf_token %}` I've tried to call that act_id value a number of different ways, but it all ends up with the same error.
    – jabs Aug 27 '12 at 22:00
  • Also, when I tried a reverse lookup `reverse('poc.views.activity_save_page')`, I got the same error: `File "", line 1, in File "/usr/local/lib/python2.7/dist-packages/django/core/urlresolvers.py", line 391, in reverse *args, **kwargs))) File "/usr/local/lib/python2.7/dist-packages/django/core/urlresolvers.py", line 337, in reverse "arguments '%s' not found." % (lookup_view_s, args, kwargs)) NoReverseMatch: Reverse for 'poc.views.activity_save_page' with arguments '()' and keyword arguments '{}' not found.` Not sure if I did that right... – jabs Aug 27 '12 at 22:05
  • when I remove any reference to the variable in the template tag `{% url activity.views.activity_save_page %}` and just call the page, the view forms correctly and accepts input, but will not update or insert into the database. How can I figure out where the view is failing? – jabs Aug 28 '12 at 02:04
  • @jabs Try `{% url activity.views.activity_save_page act_id%}` in template. Note you need to pass `act_id` parameter to your template. – Rohan Aug 28 '12 at 02:14
  • unfortunately, this also returns the `Caught NoReverseMatch` error. I've tried many different calls to return the id value, but I haven't found what works yet. – jabs Aug 28 '12 at 03:14
  • @jabs URL tag should be `{% url 'activity.views.activity_save_page' act_id %}`, single quotes around the view. If this doesn't work, may be you should ask another question with details specific to this error. – Rohan Aug 28 '12 at 04:25
  • Unfortunately, I wasn't able to make it work. Thanks for looking at it and (+1) for showing me an example of how to directly save the form without creating the objects. – jabs Aug 28 '12 at 17:01