asaglimbeni / django-datetime-widget

Bootstrap django-datetime-widget is a simple and clean widget for DateField, Timefiled and DateTimeField in Django framework. It is based on Bootstrap datetime picker, supports both Bootstrap 3 and Bootstrap 2
Other
219 stars 125 forks source link

Widget chokes in testing.. #5

Open mattlinares opened 11 years ago

mattlinares commented 11 years ago

Thanks so much for this plugin. It's working well on the front end. However, I'm finding this bug in testing. Seems to choke in this widget. I can't work out why. I've tried to work through what's happening but can't find the issue. Can you help? Thanks again!

The test adds an object using a date and stumbles in its attempt.

    response = self.client.post('/events/new/', {'name': 'Thingie', 
        'location': 'Wels', 'date': '2014-09-04 16:00:00.000000'})

ERROR: test_MyView (crewcal.tests.ResponseTest)
  ----------------------------------------------------------------------
  Traceback (most recent call last):
    File "/Users/methuselah/code/django/ssc/main/../crewcal/tests.py", line 56, in test_MyView
      'location': 'Well Street', 'date': '2014-09-04 16:00:00.000000'})
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/test/client.py", line 449, in post
      response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/test/client.py", line 262, in post
      return self.request(**r)
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/core/handlers/base.py", line 111, in get_response
      response = callback(request, *callback_args, **callback_kwargs)
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 20, in _wrapped_view
      return view_func(request, *args, **kwargs)
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 20, in _wrapped_view
      return view_func(request, *args, **kwargs)
    File "/Users/methuselah/code/django/ssc/main/../crewcal/views.py", line 118, in new_event
      if form.is_valid():
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/forms/forms.py", line 124, in is_valid
      return self.is_bound and not bool(self.errors)
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/forms/forms.py", line 115, in _get_errors
      self.full_clean()
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/forms/forms.py", line 270, in full_clean
      self._clean_fields()
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/forms/forms.py", line 281, in _clean_fields
      value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
    File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/datetimewidget/widgets.py", line 91, in value_from_datadict
      D = to_current_timezone(datetime.strptime(date_time[0], self.format))
  TypeError: must be string, not None
asaglimbeni commented 11 years ago

Hi, I'm happy to help you. I never use widget in a test environment, but from your trace it's seems that the widget is not initialized correctly. The problem is that self.widgets is empty so i sugget if you don't use init you have to put the right inputWidget inside widgets tuple like in init :https://github.com/asaglimbeni/django-datetime-widget/blob/develop/datetimewidget/widgets.py#L80-82. Remember to use last version of widget 0.5.3.a use 'pip install django-datetime-widget' to update. Thank you for your contribution. Alfredo

mattlinares commented 11 years ago

Thanks for the quick response. I understand what is happening now. Since I'm testing with a POST and my view deals with POSTS like:

def new_event(request):
    if request.method == 'POST':
        form = EventForm(request.POST, request=request

as you the form and widget is not being initialised.

I'm not sure how I would put the input widget inside the widgets tuple in testing though. My test looks like this:

    response = self.client.post('/events/new/', {'name': 'Wells', 
        'location': 'Wells', 'date': '2014-09-04 16:00:00.000000'})
    self.assertEqual(response.status_code, 302)

Is there a way I can hook into the form widget initialisation sequence with a testing framework?

Don't worry if you can't help.

Thanks again! Matt

asaglimbeni commented 11 years ago

Hi Matt, Yes from the test is impossible to put widgetInput. Can you show me how is build EventForm? Are you sure that 'date': .... is the same fileds id of the Datetimewidget?

mattlinares commented 11 years ago

Thanks Alfredo,

Here's the form builder and below is the related view.

class EventForm(forms.Form):

def __init__(self, *args, **kwargs):
    self.request = kwargs.pop('request', None)
    super(EventForm, self).__init__(*args, **kwargs)

name = forms.CharField(max_length=1024,
        initial="Give short",
        widget=forms.TextInput(attrs={'onfocus':'if(this.value=="Give short") this.value="";'}))
location = forms.CharField(max_length=1024,
        initial="Be specific",
        widget=forms.TextInput(attrs={'onfocus':'if(this.value=="Be specific") this.value="";'}))
dateTimeOptions = {
        'format': 'dd/mm/yyyy HH:ii P',
        'autoclose': 'true',
        'showMeridian': 'true',
        }
date = forms.DateTimeField(label="Date and time",widget=DateTimeWidget(options=dateTimeOptions,
        attrs={'id':"date-time"}))
host_act = forms.CharField(max_length=1024,
        initial="What act will you bring, if any?", required=False,
        widget=forms.TextInput(attrs={'onfocus':'if(this.value=="What act will you bring, if any?") this.value="";'}))
description = forms.CharField(required=False, max_length=10240,
        initial="Give some details. Any activities prepared?",
        widget=forms.Textarea(attrs={'onfocus':'if(this.value=="Give some details. Any activities prepared?") this.value="";'}))

views.py

def new_event(request):
if request.method == 'POST':
    form = EventForm(request.POST, request=request)
    if form.is_valid():
        event = Event(
                date = form.cleaned_data['date'],
                name = form.cleaned_data['name'],
                description = form.cleaned_data['description'],
                location = form.cleaned_data['location'],
                host = request.user,
                host_act = form.cleaned_data['host_act']
                )
        event.save()

        member = request.user.get_profile()
        member.event_commitments.add(event)
        event_url = reverse('events',kwargs={'event_id':event.id})
        user_list = User.objects.filter(userprofile__receive_email__exact=True)
        mailing_list = []
        for member in user_list:
            mailing_list.append(member.email)

        subject = 'event needs crew'
        username = request.user.username
        message = ('%s has proposed The event will only be publicised when there is enough crew.' % (username.capitalize, event.location, event.date, event_url))
        send_mail(subject, message, 'info', mailing_list, fail_silently=False)

        return HttpResponseRedirect(event.url)
else:
    form = EventForm()
return render_to_response('new_event.html',{'form':form},
                          context_instance=RequestContext(request))
asaglimbeni commented 11 years ago

Try to run this Test:

response = self.client.post('/events/new/', {'name': 'Wells', 'location': 'Wells', 'date-time': '2014-09-04 16:00:00.000000'}) self.assertEqual(response.status_code, 302)

Let me know if it works

mattlinares commented 11 years ago

That seems to throw the same error.

======================================================================
ERROR: test_MyView (crewcal.tests.ResponseTest)
----------------------------------------------------------------------
Traceback (most recent call last):

File "/Users/methuselah/code/django/ssc/main/../crewcal/tests.py", line 56, in test_MyView 'location': 'Wells', 'date-time': '2014-09-04 16:00:00.000000'}) File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/test/client.py", line 449, in post response = super(Client, self).post(path, data=data, content_type=content_type, extra) File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/test/client.py", line 262, in post return self.request(r) File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/core/handlers/base.py", line 111, in get_response response = callback(request, _callback_args, _callback_kwargs) File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 20, in _wrapped_view return view_func(request, _args, _kwargs) File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 20, in _wrapped_view return view_func(request, _args, *_kwargs) File "/Users/methuselah/code/django/ssc/main/../crewcal/views.py", line 117, in new_event if form.is_valid(): File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/forms/forms.py", line 124, in is_valid return self.is_bound and not bool(self.errors) File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/forms/forms.py", line 115, in _get_errors self.full_clean() File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/forms/forms.py", line 270, in full_clean self._clean_fields() File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/django/forms/forms.py", line 281, in _clean_fields value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) File "/Users/methuselah/.virtualenvs/ssc/lib/python2.7/site-packages/datetimewidget/widgets.py", line 91, in value_from_datadict D = to_current_timezone(datetime.strptime(date_time[0], self.format)) TypeError: must be string, not None


Ran 4 tests in 2.224s

FAILED (errors=2) Destroying test database for alias 'default'... (ssc)~/code/django/ssc:

mattlinares commented 11 years ago

Hi again,

The problem seems to be that the widget renames the date element as "date_0" but I was sending test data with a date field named "date". This value is not expected by other parts of the app so it chokes again later. Why does the widget rename the date value, and can you recommend a good way to alter this behaviour?

Thanks!

asaglimbeni commented 11 years ago

Hi Matt, Sorry but i did'nt have a lot of time to investigate your issue. Now for you question: The id is renamed by Django DateTimeInput, it's a normal behavior because in theory we can use more than one widget in one field. It's responsibility of value_from_datadict method to decompose the values into every input and return the correct value. I hope to find some of time next week to understand better what happen during your test. Alfredo

jimr commented 11 years ago

Hi, just wanted to say that we're hitting the same issue. The value_from_datadict function being called is on django.forms.widgets.Widget, and the implementation doesn't seem to deal with the renamed element. From pdb in that method (django/forms/widgets.py):

ipdb> print data
<QueryDict: {u'from_date': [u'2008-01-01'], u'to_date': [u'2008-01-01']}>
ipdb> print name
from_date_0

so it's returning [None]. In our case, we're using https://github.com/alex/django-filter to build the form, which may have something to do with it? Code is here:

https://github.com/openwater/h2o-really/blob/add-obs-basics/observations/filters.py

Thanks.

llazzaro commented 11 years ago

I had the same issue with unit tests, with the following date time format: %d/%m/%Y %H:%M:%S I solved it using this format: strart_date = start_date.strftime("%d/%m/%Y %H:%M") data = {"date": start_date} form = TestForm(data)

Hope it helps

mattlinares commented 11 years ago

I am still not able to get this going. Can anyone help any further?

@llazzaro I wasn't able to work your fix into my code. I tried a few things, a bit like this:

    start_date = '2014-09-04 16:00'
    start_date = time.strftime("%d/%m/%Y %H:%M", start_date)
    response = self.client.post('/events/new/', {'name': 'Wells', 
        'location': 'Wells', 'date-time': start_date})
    self.assertEqual(response.status_code, 302)

which raised:

TypeError: argument must be 9-item sequence, not str

Can you say exactly how you did it? Thanks. Btw, your use of strftime() seems to differ from the typical: http://docs.python.org/2/library/time.html#time.strftime

Thanks!

mattlinares commented 11 years ago

I've had to go ahead testing without the widget using this fix around my forms.py code where it is specified:

http://stackoverflow.com/questions/4088253/django-how-to-detect-test-environment

I'm not entirely happy with it. Perhaps I can include testing of the widget in Selenium or something later.

Any further thoughts or resolutions gladly accepted.

asaglimbeni commented 11 years ago

Hi mattlinares, sorry for late I was realy busy on last month. I want to ask you if you have update your version of django-datetime-widget with latest version 0.6? There are some fixes and maybe some of theme fix your test environment issue.