from __future__ import unicode_literals

import unittest
from chameleon.zpt.template import Macro
from pyramid.testing import DummyRequest
import json
from webob.multidict import MultiDict
from wtforms_alchemy import ModelForm

from pyramid_views.utils import ImproperlyConfigured
from pyramid_views.views.base import View
from pyramid_views.views.edit import FormMixin, ModelFormMixin, CreateView

from tests import views
from tests.models import Artist, Author
from tests.base import BaseTest, Session
from tests.forms import AuthorForm


class FormMixinTests(BaseTest):
    def test_initial_data(self):
        """ Test instance independence of initial data dict (see #16138) """
        initial_1 = FormMixin().get_initial()
        initial_1['foo'] = 'bar'
        initial_2 = FormMixin().get_initial()
        self.assertNotEqual(initial_1, initial_2)

    def test_get_prefix(self):
        """ Test prefix can be set (see #18872) """
        test_string = 'test'

        get_request = DummyRequest()

        class TestFormMixin(FormMixin):
            request = get_request

        default_kwargs = TestFormMixin().get_form_kwargs()
        self.assertEqual('', default_kwargs.get('prefix'))

        set_mixin = TestFormMixin()
        set_mixin.prefix = test_string
        set_kwargs = set_mixin.get_form_kwargs()
        self.assertEqual(test_string, set_kwargs.get('prefix'))


class BasicFormTests(BaseTest):
    urls = 'generic_views.urls'

    def test_post_data(self):
        view = views.ContactView.as_view()
        res = view(DummyRequest(method='POST', params=MultiDict(name='Me', message='Hello')))
        self.assertEqual(res.status_code, 302)
        self.assertEqual(res.headers['Location'], '/list/authors/')


class ModelFormMixinTests(BaseTest):
    def test_get_form(self):
        form_class = views.AuthorGetQueryFormView().get_form_class()
        self.assertEqual(form_class.Meta.model, Author)

    def test_get_form_checks_for_object(self):
        mixin = ModelFormMixin()
        mixin.request = DummyRequest()
        self.assertEqual({'data': {}, 'prefix': '', 'obj': None},
                         mixin.get_form_kwargs())


class CreateViewTests(BaseTest):
    urls = 'generic_views.urls'

    def test_create(self):
        view = views.AuthorCreate.as_view()
        res = view(DummyRequest())
        self.assertEqual(res.status_code, 200)
        self.assertIsInstance(res.context['form'], ModelForm)
        self.assertIsInstance(res.context['view'], View)
        self.assertFalse('object' in res.context)
        self.assertFalse('author' in res.context)
        self.assertTemplateUsed(res, 'tests:templates/author_form.html')
        self.assertIsInstance(res.context['macros']['my_macros']['testmacro'], Macro)

        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe', 'slug': 'randall-munroe'})
        ))
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/list/authors/')
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])

    def test_create_invalid(self):
        view = views.AuthorCreate.as_view()
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'A' * 101, 'slug': 'randall-munroe'})
        ))
        self.assertEqual(res.status_code, 200)
        self.assertTemplateUsed(res, 'tests:templates/author_form.html')
        self.assertEqual(len(res.context['form'].errors), 1)
        self.assertEqual(Session.query(Author).count(), 0)

    def test_create_with_object_url(self):
        view = views.ArtistCreate.as_view()
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Rene Magritte'})
        ))
        self.assertEqual(res.status_code, 302)
        artist = Session.query(Artist).filter(Artist.name=='Rene Magritte').one()
        self.assertRedirects(res, '/detail/artist/%d/' % artist.id)
        self.assertQuerysetEqual(Session.query(Artist).all(), ['<Artist: Rene Magritte>'])

    def test_create_with_redirect(self):
        view = views.NaiveAuthorCreate.as_view(success_url='/edit/authors/create/')
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe', 'slug': 'randall-munroe'})
        ))
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/edit/authors/create/')
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])

    def test_create_with_interpolated_redirect(self):
        view = views.NaiveAuthorCreate.as_view(success_url='/edit/author/%(id)d/update/')
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe', 'slug': 'randall-munroe'})
        ))
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])
        self.assertEqual(res.status_code, 302)
        id = Session.query(Author).all()[0].id
        self.assertRedirects(res, '/edit/author/%d/update/' % id)

    def test_create_with_special_properties(self):
        view = views.SpecializedAuthorCreate.as_view()
        res = view(DummyRequest())
        self.assertEqual(res.status_code, 200)
        self.assertIsInstance(res.context['form'], AuthorForm)
        self.assertFalse('object' in res.context)
        self.assertFalse('author' in res.context)
        self.assertTemplateUsed(res, 'tests:templates/form.html')

        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe', 'slug': 'randall-munroe'})
        ))
        self.assertEqual(res.status_code, 302)
        obj = Session.query(Author).filter(Author.slug=='randall-munroe').one()
        self.assertRedirects(res, '/detail/author/%s/' % obj.id)
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])

    def test_create_without_redirect(self):
        view = views.NaiveAuthorCreate.as_view()
        request = DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe', 'slug': 'randall-munroe'})
        )
        # Should raise exception -- No redirect URL provided, and no get_absolute_url provided
        self.assertRaises(ImproperlyConfigured, view, request)

    @unittest.skip('login_required() decorator is part of Django, so skipping this feature for now')
    def test_create_restricted(self):
        view = views.AuthorCreateRestricted.as_view()
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe', 'slug': 'randall-munroe'})
        ))

        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/')

    def test_create_view_with_restricted_fields(self):

        class MyCreateView(CreateView):
            model = Author
            fields = ['name']
        form_class = MyCreateView().get_form_class()
        fields = [field.name for field in form_class()]
        self.assertEqual(fields, ['name'])

    def test_create_view_all_fields(self):
        class MyCreateView(CreateView):
            model = Author
            # Whereas in Django one uses '__all__', wtforms_alchemy
            # supports a None value and intelligently determining fields to use
            # fields = '__all__'
        form_class = MyCreateView().get_form_class()
        fields = [field.name for field in form_class()]
        self.assertEqual(fields, ['name', 'slug'])

    @unittest.skip('Omitting the "fields" attribute is actually supported')
    def test_create_view_without_explicit_fields(self):
        class MyCreateView(CreateView):
            model = Author

        message = (
            "Using ModelFormMixin (base class of MyCreateView) without the "
            "'fields' attribute is prohibited."
        )
        with self.assertRaisesMessage(ImproperlyConfigured, message):
            MyCreateView().get_form_class()

    def test_create_endpoint(self):
        view = views.AuthorCreate.as_view(endpoint=True, success_url=None)
        res = view(DummyRequest())
        self.assertEqual(res.status_code, 501)
        self.assertEqual(res.body, '')

        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe', 'slug': 'randall-munroe'})
        ))
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res.body, '')
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])

    def test_create_endpoint_with_url(self):
        view = views.AuthorCreate.as_view(endpoint=True)
        res = view(DummyRequest())
        self.assertEqual(res.status_code, 501)
        self.assertEqual(res.body, '')

        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe', 'slug': 'randall-munroe'})
        ))
        self.assertEqual(res.status_code, 302)
        self.assertEqual(res.body, '')
        self.assertRedirects(res, '/list/authors/')
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])

    def test_create_endpoint_invalid(self):
        view = views.AuthorCreate.as_view(endpoint=True)
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'A' * 101, 'slug': 'randall-munroe'})
        ))
        self.assertEqual(res.status_code, 400)
        self.assertEqual(Session.query(Author).count(), 0)
        decoded = json.loads(res.body)
        self.assertIn('errors', decoded)


class UpdateViewTests(BaseTest):
    urls = 'generic_views.urls'

    def test_update_post(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.AuthorUpdate.as_view()
        res = view(DummyRequest(), pk=a.id)
        self.assertEqual(res.status_code, 200)
        self.assertIsInstance(res.context['form'], ModelForm)
        self.assertEqual(res.context['form']._fields['name'].object_data, 'Randall Munroe')
        self.assertEqual(res.context['form']._fields['slug'].object_data, 'randall-munroe')
        self.assertEqual(res.context['object'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertEqual(res.context['author'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertTemplateUsed(res, 'tests:templates/author_form.html')
        self.assertIsInstance(res.context['macros']['my_macros']['testmacro'], Macro)

        # Modification with both POST and PUT (browser compatible)
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe (xkcd)', 'slug': 'randall-munroe'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/list/authors/')
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe (xkcd)>'])

    def test_update_put(self):
        # Note that this test doesn't work under Django as it doesn't process
        # PUT data specially, but it does work here as we are interacting
        # directly with the view.

        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.AuthorUpdate.as_view()
        res = view(DummyRequest(), pk=a.id)
        self.assertEqual(res.status_code, 200)
        self.assertTemplateUsed(res, 'tests:templates/author_form.html')

        res = view(DummyRequest(
            method='PUT',
            params=MultiDict({'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}),
        ), pk=a.id)
        # Here is the expected failure. PUT data are not processed in any special
        # way by django. So the request will equal to a POST without data, hence
        # the form will be invalid and redisplayed with errors (status code 200).
        # See also #12635
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/list/authors/')
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe (author of xkcd)>'])

    def test_update_invalid(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.AuthorUpdate.as_view()
        res = view(DummyRequest(
            method='PUT',
            params=MultiDict({'name': 'a' * 101, 'slug': 'randall-munroe'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 200)
        self.assertTemplateUsed(res, 'tests:templates/author_form.html')
        self.assertEqual(len(res.context['form'].errors), 1)
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])

    def test_update_with_object_url(self):
        a = self.artist(name='Rene Magritte')
        view = views.ArtistUpdate.as_view()
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Rene Magritte'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/detail/artist/%d/' % a.id)
        self.assertQuerysetEqual(Session.query(Artist).all(), ['<Artist: Rene Magritte>'])

    def test_update_with_redirect(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.AuthorUpdate.as_view(success_url='/edit/authors/create/')
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/edit/authors/create/')
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe (author of xkcd)>'])

    def test_update_with_interpolated_redirect(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.NaiveAuthorUpdate.as_view(success_url='/edit/author/%(id)d/update/')
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}),
        ), pk=a.id)
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe (author of xkcd)>'])
        self.assertEqual(res.status_code, 302)
        pk = Session.query(Author).all()[0].id
        self.assertRedirects(res, '/edit/author/%d/update/' % pk)

    def test_update_with_special_properties(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.SpecializedAuthorUpdate.as_view()
        res = view(DummyRequest(), pk=a.id)
        self.assertEqual(res.status_code, 200)
        self.assertIsInstance(res.context['form'], views.AuthorForm)
        self.assertEqual(res.context['object'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertEqual(res.context['thingy'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertFalse('author' in res.context)
        self.assertTemplateUsed(res, 'tests:templates/form.html')

        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/detail/author/%d/' % a.id)
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe (author of xkcd)>'])

    def test_update_without_redirect(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        # Should raise exception -- No redirect URL provided, and no
        # get_absolute_url provided
        view = views.NaiveAuthorUpdate.as_view()
        with self.assertRaises(ImproperlyConfigured):
            view(DummyRequest(
                method='POST',
                params=MultiDict({'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}),
            ), pk=a.id)

    def test_update_get_object(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.OneAuthorUpdate.as_view()
        res = view(DummyRequest())
        self.assertEqual(res.status_code, 200)
        self.assertIsInstance(res.context['form'], ModelForm)
        self.assertIsInstance(res.context['view'], View)
        self.assertEqual(res.context['object'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertEqual(res.context['author'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertTemplateUsed(res, 'tests:templates/author_form.html')

        # Modification with both POST and PUT (browser compatible)
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe (xkcd)', 'slug': 'randall-munroe'}),
        ))
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/list/authors/')
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe (xkcd)>'])

    def test_update_endpoint(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.AuthorUpdate.as_view(endpoint=True, success_url=None)
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe (xkcd)', 'slug': 'randall-munroe'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 200)
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe (xkcd)>'])

    def test_update_endpoint_with_url(self):
        a = self.artist(name='Rene Magritte')
        view = views.ArtistUpdate.as_view(endpoint=True)
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Rene Magritte'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/detail/artist/%d/' % a.id)
        self.assertQuerysetEqual(Session.query(Artist).all(), ['<Artist: Rene Magritte>'])

    def test_update_invalid_endpoint(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.AuthorUpdate.as_view(endpoint=True, success_url=None)
        res = view(DummyRequest(
            method='PUT',
            params=MultiDict({'name': 'a' * 101, 'slug': 'randall-munroe'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 400)
        decoded = json.loads(res.body)
        self.assertIn('errors', decoded)
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])

    def test_update_invalid_endpoint_with_url(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.AuthorUpdate.as_view(endpoint=True)
        res = view(DummyRequest(
            method='PUT',
            params=MultiDict({'name': 'a' * 101, 'slug': 'randall-munroe'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 400)
        decoded = json.loads(res.body)
        self.assertIn('errors', decoded)
        self.assertQuerysetEqual(Session.query(Author).all(), ['<Author: Randall Munroe>'])

    def test_partial_update(self):
        a = self.author(
            name='Randall Munroe',
            slug='randall-munroe',
        )
        view = views.AuthorUpdate.as_view(partial_updates=True)
        res = view(DummyRequest(), pk=a.id)
        self.assertEqual(res.status_code, 200)
        self.assertIsInstance(res.context['form'], ModelForm)
        self.assertEqual(res.context['form']._fields['name'].object_data, 'Randall Munroe')
        self.assertEqual(res.context['form']._fields['slug'].object_data, 'randall-munroe')
        self.assertEqual(res.context['object'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertEqual(res.context['author'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertTemplateUsed(res, 'tests:templates/author_form.html')
        self.assertIsInstance(res.context['macros']['my_macros']['testmacro'], Macro)

        # Modification with both POST and PUT (browser compatible)
        res = view(DummyRequest(
            method='POST',
            params=MultiDict({'name': 'Randall Munroe (xkcd)'}),
        ), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/list/authors/')
        author = Session.query(Author).one()
        self.assertQuerysetEqual(author.name, 'Randall Munroe (xkcd)')
        self.assertQuerysetEqual(author.slug, 'randall-munroe')


class DeleteViewTests(BaseTest):

    def test_delete_by_post(self):
        a = self.author(name='Randall Munroe', slug='randall-munroe')
        view = views.AuthorDelete.as_view()
        res = view(DummyRequest(), pk=a.id)
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res.context['object'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertEqual(res.context['author'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertTemplateUsed(res, 'tests:templates/author_confirm_delete.html')
        self.assertIsInstance(res.context['macros']['my_macros']['testmacro'], Macro)

        # Deletion with POST
        res = view(DummyRequest(method='POST'), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/list/authors/')
        self.assertQuerysetEqual(Session.query(Author).all(), [])

    def test_delete_by_delete(self):
        # Deletion with browser compatible DELETE method
        a = self.author(name='Randall Munroe', slug='randall-munroe')
        view = views.AuthorDelete.as_view()
        res = view(DummyRequest(method='DELETE'), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/list/authors/')
        self.assertQuerysetEqual(Session.query(Author).all(), [])

    def test_delete_with_redirect(self):
        a = self.author(name='Randall Munroe', slug='randall-munroe')
        view = views.AuthorDelete.as_view(success_url='/edit/authors/create/')
        res = view(DummyRequest(method='POST'), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/edit/authors/create/')
        self.assertQuerysetEqual(Session.query(Author).all(), [])

    def test_delete_with_interpolated_redirect(self):
        a = self.author(name='Randall Munroe', slug='randall-munroe')
        view = views.AuthorDelete.as_view(success_url='/edit/authors/create/?deleted=%(id)s')
        res = view(DummyRequest(method='POST'), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/edit/authors/create/?deleted=%d' % a.id)
        self.assertQuerysetEqual(Session.query(Author).all(), [])

    def test_delete_with_special_properties(self):
        a = self.author(name='Randall Munroe', slug='randall-munroe')
        view = views.SpecializedAuthorDelete.as_view()
        res = view(DummyRequest(), pk=a.id)
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res.context['object'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertEqual(res.context['thingy'], Session.query(Author).filter(Author.id==a.id).one())
        self.assertFalse('author' in res.context)
        self.assertTemplateUsed(res, 'tests:templates/confirm_delete.html')

        res = view(DummyRequest(method='POST'), pk=a.id)
        self.assertEqual(res.status_code, 302)
        self.assertRedirects(res, '/list/authors/')
        self.assertQuerysetEqual(Session.query(Author).all(), [])

    def test_delete_without_redirect(self):
        a = self.author(name='Randall Munroe', slug='randall-munroe')
        # Should raise exception -- No redirect URL provided, and no
        # get_absolute_url provided
        view = views.NaiveAuthorDelete.as_view()
        with self.assertRaises(ImproperlyConfigured):
            res = view(DummyRequest(method='POST'), pk=a.id)
