⁠⁠⁠# -- coding: utf-8 --
from django.views.generic import ListView, View
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormView, DeleteView
from django.core.serializers.json import DjangoJSONEncoder
from django.forms.models import inlineformset_factory, modelformset_factory
from django.http import HttpResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.db.models import Q, F
from django.contrib.auth.models import User
from django.http import Http404, HttpResponseBadRequest
from django.conf.urls import include, url
from django.contrib.auth import authenticate, login, logout
from django import forms
import sys, inspect
import datetime
from django.core import serializers
import json
from auths import SupraAuthenticationMixin
from django.db import models
from django.db.models import fields
from decimal import Decimal
from django.db.models.fields.files import ImageFieldFile, FieldFile
from django.middleware.csrf import get_token

"""
        @name: SupraConf
        @author: exile.sas
        @date: 21/02/2016
        @licence: creative commons
"""

class SupraConf:
        body = False
        template = False
        date_format = "%d/%m/%Y"
        datetime_format = "%d/%m/%Y %I:%M%p"
        content_type = "application/json; charset=utf-8"

        ACCECC_CONTROL = {
                "allow": False,
                "origin": "*",
                "credentials": "true",
                "headers": "accept, content-type",
                "max_age": "1728000",
                "methods": "POST, GET, OPTIONS, DELETE"
        }

#end class

def access_control(old):
        def new(*args, **kwargs):
                httpresponse = old(*args, **kwargs)
                if SupraConf.ACCECC_CONTROL['allow'] and isinstance(httpresponse, HttpResponse):
                        httpresponse["Access-Control-Allow-Origin"] = SupraConf.ACCECC_CONTROL['origin'];
                        httpresponse["Access-Control-Allow-Credentials"] = SupraConf.ACCECC_CONTROL['credentials'];
                        httpresponse["Access-Control-Allow-Headers"] = SupraConf.ACCECC_CONTROL['headers'];
                        httpresponse["Access-Control-Max-Age"] = SupraConf.ACCECC_CONTROL['max_age'];
                        httpresponse["Access-Control-Allow-Methods"] = SupraConf.ACCECC_CONTROL['methods'];
                #end if
                return httpresponse
        # end def
        return new
# end def

"""
        @name: SupraListView
        @author: exile.sas
        @date: 13/10/2015
        @licence: creative commons
"""
class SupraListView(ListView, SupraAuthenticationMixin):
        list_display = None
        search_fields = []
        list_filter = []
        kwargs = {}
        dict_only = False
        rules = {}
        template = False
        template_name = "supra/list.html"
        request = None
        search_key = 'search'
        date_format = SupraConf.date_format
        datetime_format = SupraConf.datetime_format

        @classmethod
        def as_url(cls):
                app = cls.model._name_.lower()
                return url('%s/list/$' % (app, ), cls.as_view(),)
        #end class

        def _ini_(self, dict_only = False, *args, **kwargs):
                self.dict_only = dict_only
                return super(SupraListView, self)._init_(*args, **kwargs)
        #end def

        @method_decorator(access_control)
        def dispatch(self, request, *args, **kwargs):
                auth = self.auth(request, *args, **kwargs)
                if auth:
                        return auth
                # end if
                kwargs = self.get_list_kwargs(request)
                self.template = request.GET.get('template', SupraConf.template)
                self.request = request
                return super(SupraListView, self).dispatch(request, *args, **kwargs)
        #end def

        def get_kwargs(self, request):
                kwargs = {}
                if request.method in ('GET',):
                        kwargs = request.GET
                #end def
                return kwargs
        #end def

        def get_list_kwargs(self, request):
                kwargs = self.get_kwargs(request)
                for field in self.list_filter:
                        if field in kwargs:
                                kwarg = kwargs[field]
                                self.kwargs[field] = kwarg
                        #end if
                #end for
                if self.search_key in kwargs:
                        self.kwargs[self.search_key] = kwargs[self.search_key]
                #end def
                return kwargs
        #end def

        def get_queryset(self):
                queryset = super(SupraListView, self).get_queryset()
                q = Q()
                for column in self.list_filter:
                        if column in self.kwargs:
                                filter = self.kwargs[column]
                                kwargs = {
                                        column: filter,
                                }
                                q = Q(q & Q(**kwargs))
                        #end if
                        queryset = queryset.filter(q)
                #end for
                q = False
                if self.search_key in self.kwargs:
                        for column in self.search_fields:
                                search = self.kwargs[self.search_key]
                                kwargs = {
                                        '{0}__{1}'.format(column, 'icontains'): search, 
                                }
                                if q:
                                        q = q | Q(**kwargs)
                                else:
                                        q = Q(**kwargs)
                                # end if
                        #end for
                        if not q:
                                raise Exception('The view has not defined any search_fields')
                        # end if
                        queryset = queryset.filter(q)
                #end if
                queryset = queryset.filter(**self.rules)
                return queryset
        #end def

        def get_context_data(self, **kwargs):
                context = super(SupraListView, self).get_context_data(**kwargs)
                context['num_rows'] = context['object_list'].count()
                context['object_list'] = context['object_list']
                return context
        #end def

        def get_object_list(self, object_list):
                queryset = object_list
                self_list = []
                list_display = []
                if self.list_display:
                        for i in range(len(self.list_display)):
                                display = self.list_display[i]
                                if isinstance(display, tuple):
                                        if hasattr(self, display[0]):
                                                self_list.append(display)
                                        else:
                                                list_display.append(display)
                                        # end if
                                elif hasattr(self, display):
                                        self_list.append(display)
                                else:
                                        list_display.append(display)
                                # end def
                        # end for
                        if hasattr(self, 'Renderer'):
                                renderers = dict((key, F(value)) for key, value in self.Renderer._dict.iteritems() if not callable(value) and not key.startswith('_'))
                                queryset = queryset.annotate(**renderers)
                        #end if
                #end if

                def extra(dct, objlist, index, row):
                        obj = list(objlist)[index]
                        print obj.pk, index
                        for slf in self_list:
                                if isinstance(slf, tuple):
                                        encode = slf[1]
                                        slf = slf[0]
                                else:
                                        encode = None
                                # end if
                                attr = getattr(self, slf)
                                if callable(attr):
                                        dct[slf] = attr(obj, row)
                                else:
                                        dct[slf] = attr
                                # end if
                                if encode == 'json':
                                        dct[slf] = json.loads(dct[slf])
                                # end if
                        # end for
                        return dct
                # end def
                return self.format_json(queryset, extra, list_display=list_display)
        #end def

        @classmethod
        def format_json(cls, queryset, extra=None, time=0, list_display=[]):
                from django.db.models.query import QuerySet
                object_list = []

                if isinstance(queryset, QuerySet):
                        list_d = []
                        for lis in list_display:
                                if isinstance(lis, tuple):
                                        list_d.append(lis[0])
                                else:
                                        list_d.append(lis)
                                # end if
                        # end for
                        rows = queryset.values(*list_d)
                else:
                        rows = queryset
                # end if
                i = 0
                for row in rows:
                        dct = {}

                        if isinstance(row, dict):
                                if isinstance(queryset[i], dict) and not 'pk' in queryset[i]:
                                        pk = queryset[i].pk
                                        row['pk'] = pk
                                # end if
                        # end if
                        obj = row

                        if list_display == [] or list_display == None:
                                if isinstance(row, dict):
                                        list_display = row
                                else:
                                        list_display = row._dict_
                                        row = row._dict_
                                # end if
                        # end if
                        for col in list_display:
                                if isinstance(col, tuple):
                                        encode = col[1]
                                        col = col[0]
                                else:
                                        encode = None
                                # end if

                                if col != '_state' and col in row:
                                        val = row[col]
                                else:
                                        val = None
                                # end if

                                if encode == 'json':
                                        if isinstance(val, dict) or isinstance(val, list):
                                                dct[col] = val
                                        else:
                                                dct[col] = json.loads(val)
                                        # end if
                                elif isinstance(val, Decimal):
                                        dct[col] = unicode(val)
                                elif isinstance(val, datetime.datetime):
                                        dct[col] = val.strftime(cls.datetime_format)
                                elif isinstance(val, datetime.date):
                                        dct[col] = val.strftime(cls.date_format)
                                elif isinstance(val, dict) or isinstance(val, list):
                                        dct[col] = cls.format_json(val, extra, time=time + 1)
                                elif hasattr(val, 'all'):
                                        dct[col] = cls.format_json(val.all(), extra, time=time + 1)
                                elif isinstance(val, models.Model):
                                        dct[col] = unicode(val)
                                elif isinstance(val, ImageFieldFile):
                                        if val:
                                                dct[col] = str(val.url)
                                        else:
                                                dct[col] = None
                                        # end if
                                elif isinstance(val, FieldFile):
                                        if val:
                                                dct[col] = str(val.url)
                                        else:
                                                dct[col] = None
                                        # end if
                                else:
                                        dct[col] = val
                                # end if
                                #setattr(obj, col, dct[col])
                        # end for
                        if extra and callable(extra):
                                dct = extra(dct, queryset, i, row)
                        # end if
                        object_list.append(dct)
                        i = i + 1
                # end for
                return object_list
        # end def
        @method_decorator(access_control)
        def render_to_response(self, context, **response_kwargs):
                json_dict = {}

                object_list = self.get_object_list(context["object_list"])

                page_obj = context["page_obj"]
                paginator = context["paginator"]
                num_rows = context["num_rows"]
                if page_obj:
                        if page_obj.has_previous():
                                json_dict["previous"] = page_obj.previous_page_number()
                        #end if
                        if page_obj.has_next():
                                json_dict["next"] = page_obj.next_page_number()
                        #endif
                #end if
                if paginator:
                        json_dict["count"] = paginator.count
                        json_dict["num_pages"] = paginator.num_pages
                        json_dict["page_range"] = str(paginator.page_range)
                #end if
                json_dict["num_rows"] = num_rows
                json_dict["object_list"] = object_list
                if self.dict_only:
                        return json_dict
                #end if
                if self.template:
                        json_dict['search_fields'] = self.search_fields
                        return render(self.request, self.template_name, json_dict)
                #end if
                return HttpResponse(json.dumps(json_dict, cls=DjangoJSONEncoder, ensure_ascii=False), content_type=SupraConf.content_type)
        #end def

#end class

class SupraDetailView(DetailView, SupraAuthenticationMixin):
        fields = None
        extra_fields = {}
        template_name = "supra/list.html"
        dict_only = False

        @classmethod
        def as_url(cls):
                app = cls.model._name_.lower()
                return url(r'^%s/(?P<pk>\d+)/$' % (app, ), cls.as_view(), )
        #end class

        def _ini_(self, dict_only = False, *args, **kwargs):
                self.dict_only = dict_only
                return super(SupraDetailView, self)._init_(*args, **kwargs)
        #end def

        @method_decorator(access_control)
        def dispatch(self, request, *args, **kwargs):
                auth = self.auth(request, *args, **kwargs)
                self.kwargs = kwargs
                if auth:
                        return auth
                # end if
                if hasattr(self, 'Renderer'):
                        renderers = dict((key, value) for key, value in self.Renderer._dict.iteritems() if not key.startswith('_'))
                        for renderer in renderers:
                                listv = renderers[renderer](dict_only=True)
                                ref = self.get_reference(listv)
                                if ref:
                                        pk = kwargs['pk']
                                        listv.rules[ref.name] = pk
                                        self.extra_fields[renderer] = listv.dispatch(request, *args, **kwargs)
                                #end def
                        #end for
                #end if
                return super(SupraDetailView, self).dispatch(request, request, *args, **kwargs) 
        #end def

        def get_reference(self, listv):
                for field in listv.model._meta.fields:
                        if field.is_relation and field.rel.to == self.model:
                                return field
                        #end if
                #end for
                return False
        #end def

        def render_to_response(self, context, **response_kwargs):
                json_dict = {}
                if self.fields:
                        for field in self.fields:
                                json_dict[field] = getattr(context["object"], field)
                        #end for
                else:
                        fields = context["object"]._meta.fields
                        for field in fields:
                                json_dict[field.name] = getattr(context["object"], field.name)
                        #end for
                #end if
                for extra in self.extra_fields:
                        json_dict[extra] = self.extra_fields[extra]
                # end for
                json_dict['pk'] = context["object"].pk
                json_dict = SupraListView.format_json([json_dict])
                if self.dict_only:
                        return json_dict[0]
                #end if
                return HttpResponse(json.dumps(json_dict[0], cls=DjangoJSONEncoder), content_type=SupraConf.content_type)
        #enddef
#end class

class SupraFormView(FormView, SupraAuthenticationMixin):
        template_name = "supra/form.html"
        response_json = True
        inlines = []
        validated_inilines = []
        invalided_inilines = []
        body = False
        http_kwargs = {}
        initial_pk= None

        @classmethod
        def as_url(cls):
                app = cls.model._name_.lower()
                urlpatterns = [
                        url(r'^%s/form/(?P<pk>\d+)/$' % (app, ), cls.as_view()),
                        url(r'^%s/form/$' % (app, ), cls.as_view()),
                ]
                return url(r'^', include(urlpatterns))
        #end class

        @method_decorator(access_control)
        @method_decorator(csrf_exempt)
        def dispatch(self, request, *args, **kwargs):
                body = request.body
                self.body_data = request.POST.get('body',  body)
                auth = self.auth(request, *args, **kwargs)
                if auth:
                        return auth
                # end if
                self.http_kwargs = kwargs
                return super(SupraFormView, self).dispatch(request, *args, **kwargs)
        #end def

        def get_form_class(self):
                if not self.form_class:
                        class SupraDefaultFromClass(forms.ModelForm):
                                class Meta:
                                        model = self.model
                                        exclude = []
                                #end class
                        #end class
                        self.form_class = SupraDefaultFromClass
                #end if
                return self.form_class
        #end def

        def get_form_kwargs(self):
                kwargs = super(SupraFormView, self).get_form_kwargs()
                if 'pk' in self.http_kwargs:
                        self.initial_pk = self.http_kwargs['pk']
                #end if
                if (self.body or SupraConf.body) and self.request.method in ('POST', 'PUT'):
                        try:
                                body = json.loads(self.body_data)
                                kwargs.update({
                                        'data': body
                                })
                        except ValueError as e:
                                pass
                        # end try 
                #end def
                kwargs['instance'] = self.get_instance(kwargs)
                return kwargs
        #end def

        def get_instance(self, kwargs):
                if self.initial_pk:
                        self.instance = self.model.objects.filter(pk=self.initial_pk).first()
                        if self.instance is None:
                                raise Http404
                        #end if
                #end if
                if hasattr(self,'instance'):
                        return self.instance
                #end if
                return None
        #end def

        def form_valid(self, form):
                instance = form.save()
                for inline in self.validated_inilines:
                        inline.instance = instance
                        inline.save()
                #end for
                if self.response_json:
                        json_dict = SupraListView.format_json([instance])
                        json_dict[0]["session_key"] = self.request.session.session_key
                        json_dict[0]["csrf_token"] = get_token(self.request)
                        return HttpResponse(json.dumps(json_dict[0]), status=200, content_type=SupraConf.content_type)
                else:
                        return HttpResponse(status=200)
                # end if
        #end def

        def get_context_data(self, **kwargs):
                context = super(SupraFormView, self).get_context_data(**kwargs)
                context['inlines'] = []
                for inline in self.inlines:
                        if not inline.base_model:
                                inline.base_model = self.model
                        #end if
                        if hasattr(self, 'instance'):
                                form_class = inline(request=self.request, instance=self.instance).get_form()
                        else:
                                form_class = inline().get_form_class()
                        #end if
                        context['inlines'].append(form_class)
                #end for
                return context
        #end def

        @method_decorator(csrf_exempt)
        @method_decorator(access_control)
        def post(self, request, *args, **kwargs):
                form_class = self.get_form_class()
                form = self.get_form(form_class)
                is_valid_form = form.is_valid()
                is_valid_inlines = self.is_valid_inlines()
                if is_valid_form and is_valid_inlines:
                        return self.form_valid(form)
                #end if
                return self.form_invalid(form)
        #end def

        def is_valid_inlines(self):
                self.invalided_inilines = []
                self.validated_inilines = []
                for inline in self.inlines:
                        i = inline()
                        i.base_model = self.model
                        form_class = i.get_form_class()
                        form = form_class(**self.get_form_kwargs())
                        if not form.is_valid():
                                self.invalided_inilines.append(form)
                        #end if
                        self.validated_inilines.append(form)
                #end for
                if len(self.invalided_inilines) > 0:
                        return False
                #end if
                return True
        #end for

        def form_invalid(self, form):
                errors = dict(form.errors)
                for i in self.invalided_inilines:
                        errors['inlines'] = list(i.errors)
                #end for
                return HttpResponse(json.dumps(errors), status=400, content_type=SupraConf.content_type)
        #end def

#end class


class SupraSession(SupraFormView, SupraAuthenticationMixin):
        model = User
        @method_decorator(csrf_exempt)
        def dispatch(self, request, *args, **kwargs):
                auth = self.auth(request, *args, **kwargs)
                if auth:
                        return auth
                # end if
                this = self
                class SupraDefaultFromClass(forms.ModelForm):
                        class Meta:
                                model = self.model
                                fields = ['username', 'password']
                        #end class

                        def clean(self):
                                return self.data
                        #end def

                        def save(self, commit=True):
                                return this.login(request, self.cleaned_data,)
                        #end def
                #end class
                self.form_class = SupraDefaultFromClass

                return super(SupraSession, self).dispatch(request, *args, **kwargs)

        #end def

        def login(self, request, cleaned_data):
                user = authenticate(username=cleaned_data['username'], password=cleaned_data['password'])
                if user is not None:
                        exist_obj = self.model.objects.filter(pk = user.pk).count()
                        if exist_obj and user.is_active:
                                login(request, user)
                                return user
                        #end if 
                #end if
                raise Http404
        #end def

        def delete(self, request):
                logout(request)
                return HttpResponse(status=200)
        #end def
#end class


class SupraInlineFormView(SupraFormView, SupraAuthenticationMixin):
        base_model = None
        model = None
        formset_class = None
        form_class = None
        instance = None
        no_auto_add = True

        def get_base_model(self):
                return self.base_model
        #end class

        def get_instance(self, kwargs):
                if self.initial_pk:
                        self.instance = self.get_base_model().objects.filter(pk=self.initial_pk).first()
                        if self.instance is None:
                                raise Http404
                        #end if
                #end if
                return self.instance
        #end def

        def get_form_class(self):
                if self.formset_class and self.form_class:
                        return inlineformset_factory(self.get_base_model(), self.model, form=self.form_class, formset=self.formset_class, exclude=[], extra=2, instance=None)
                else:
                        return inlineformset_factory(self.get_base_model(), self.model, exclude=[])
                #end if
        #end def
#end class

class SupraDeleteView(DeleteView, SupraAuthenticationMixin):
        template_name = "supra/delete.html"

        @classmethod
        def as_url(cls):
                app = cls.model._name_.lower()
                return url(r'^%s/delete/(?P<pk>\d+)/$' % (app, ), cls.as_view())
        #end class

        def delete(self, request, *args, **kwargs):
                self.object = self.get_object()
                self.object.delete()
                return HttpResponse(status=200)
        #end def

#end class

class SupraCRUD():
        model = None

        list_display = None
        list_filter = []
        search_fields = None
        search_key = None
        form_class = None

        inlines = []
        auto_inlines = []

        @classmethod
        def as_view(cls):

                for auto in cls.auto_inlines:
                        class SupraDefaultInlineFormView(SupraInlineFormView):
                                model = auto
                                base_model = cls.model
                        #end class
                        cls.inlines.append(SupraDefaultInlineFormView)
                #end class

                class List(SupraListView):
                        model = cls.model

                        list_display = cls.list_display
                        list_filter = cls.list_filter
                        search_fields = cls.search_fields
                        search_key = cls.search_key
                #end class
                class Form(SupraFormView):
                        model = cls.model

                        form_class = cls.form_class
                        inlines = cls.inlines
                #end class
                class Delete(SupraDeleteView):
                        model = cls.model
                #end class
                class Detail(SupraDetailView):
                        model = cls.model
                #end class
                app = cls.model._name_.lower()
                urlpatterns = [
                        Detail.as_url(),
                        List.as_url(),
                        Form.as_url(),
                        Delete.as_url(),
                ]
                return include(urlpatterns)

        #end class

        @classmethod
        def as_url(cls):
                return url(r'^', cls.as_view())
        #end class

#end class

def all_supras(view):
        def is_for_supra(obj):
                if inspect.isclass(obj):
                        return not hasattr(obj, 'no_auto_add')
                #end if
                return False
        #end def
        supras = inspect.getmembers(sys.modules[view._name_], is_for_supra)
        urls = []
        for name, supra in supras:
                urls.append(supra.as_url())
        #end for
        return urls
#end def