## Copyright 2011-2013 Luc Saffre
## This file is part of the Lino project.
## Lino is free software; you can redistribute it and/or modify 
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
## Lino is distributed in the hope that it will be useful, 
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
## GNU General Public License for more details.
## You should have received a copy of the GNU General Public License
## along with Lino; if not, see <http://www.gnu.org/licenses/>.


#~ import datetime
#~ import logging
#~ logger = logging.getLogger(__name__)

#~ from django import forms
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings

from django.contrib.auth.hashers import (
    check_password, make_password, is_password_usable)



from lino import dd
from lino.utils import babel
from lino.utils import mti
from lino.utils.xmlgen.html import E
#~ from lino.utils import choicelists
from lino.utils.choosers import chooser
from lino import mixins
from lino.core import actions

#~ from lino.mixins import PersonMixin
#~ from lino.modlib.contacts.models import Contact
#~ from lino.modlib.contacts import models as contacts

#~ contacts = dd.resolve_app('contacts')

#~ from lino.core.perms import UserLevels

#~ if settings.LINO.is_installed('users') and settings.LINO.user_model != 'users.User':
    #~ raise Exception("""\
#~ You are using lino.modlib.users in your INSTALLED_APPS, 
#~ but settings.LINO.user_model is %r (should be 'users.User').
#~ """ % settings.LINO.user_model)

class User(mixins.CreatedModified):
    """
    Represents a User of this site.
    
    This version of the Users table is used on Lino sites with
    :doc:`/topics/http_auth`. 
    
    Only username is required. Other fields are optional.
    
    There is no password field since Lino is not responsible for authentication.
    New users are automatically created in this table when 
    Lino gets a first request with a username that doesn't yet exist.
    It is up to the local system administrator to manually fill then 
    fields like first_name, last_name, email, access rights for the new user.    
    """
    
    preferred_foreignkey_width = 15 
    
    hidden_columns = 'password remarks'
    
    #~ authenticated = True
    
    class Meta:
        verbose_name = _('User')
        verbose_name_plural = _('Users')
        ordering = ['last_name','first_name']

    username = models.CharField(_('Username'), max_length=30, 
        unique=True, 
        help_text=_("""
        Required. Must be unique. 
        """))
        
    password = models.CharField(_('Password'), max_length=128)
        
    profile = dd.UserProfiles.field(blank=True)
    
    first_name = models.CharField(_('First name'), max_length=30, blank=True)
    last_name = models.CharField(_('Last name'), max_length=30, blank=True)
    email = models.EmailField(_('e-mail address'), blank=True)
    
    remarks = models.TextField(_("Remarks"),blank=True) # ,null=True)
    
    language = babel.LanguageField(default=models.NOT_PROVIDED)
    
    if settings.LINO.is_installed('contacts'):
      
        partner = models.ForeignKey('contacts.Partner',blank=True,null=True)
        
    else:
      
        partner = dd.DummyField()
    

    def __unicode__(self):
        return self.get_full_name()
        

    def get_full_name(self):
        "Returns the first_name plus the last_name, with a space in between."
        if not self.first_name and not self.last_name:
            return self.username
        return u'%s %s' % (self.first_name.strip(), self.last_name.strip())
        
    @dd.displayfield(_("Name"),max_length=15)
    def name_column(self,request):
        #~ return join_words(self.last_name.upper(),self.first_name)
        return unicode(self)


    if settings.LINO.is_installed('contacts'):
        def get_person(self):
            if self.partner:
                return self.partner.get_mti_child('person')
    else:
        def get_person(self):
            return None
    
    person = property(get_person)

    def get_row_permission(self,ar,state,ba):
        """
        Only system managers may edit other users.
        See also :meth:`User.disabled_fields`.
        """
        #~ print 20120621, self, user, state, action
        if not ba.action.readonly:
            user = ar.get_user()
            if user != self:
                if user.profile.level < dd.UserLevels.admin: 
                    return False
        return super(User,self).get_row_permission(ar,state,ba)
        #~ return False
        
    def disabled_fields(self,ar):
        """
        Only System admins may change the `profile` of users.
        See also :meth:`Users.get_row_permission`.
        """
        #~ if ar.get_user().is_superuser: 
        #~ if request.user.is_superuser: 
        if ar.get_user().profile.level < dd.UserLevels.admin:
            l = ['profile']
        else:
            l = []
        #~ if self.profile:
            #~ l += settings.LINO.user_profile_fields
        return l
        
    def full_clean(self,*args,**kw):
        p = self.person
        if p is not None:
            for n in ('first_name','last_name','email','language'):
                if not getattr(self,n):
                    setattr(self,n,getattr(p,n))
            #~ self.language = p.language
        if not self.language:
            self.language = babel.DEFAULT_LANGUAGE
        if not self.password:
            self.set_unusable_password()
        super(User,self).full_clean(*args,**kw)
        
    #~ def save(self,*args,**kw):
        #~ super(User,self).save(*args,**kw)
        
    def get_received_mandates(self):
        #~ return [ [u.id,_("as %s")%u] for u in self.__class__.objects.all()]
        return [ [u.id,unicode(u)] for u in self.__class__.objects.all()]
        #~ return self.__class__.objects.all()

    # the following methods are unchanged copies from Django's User model
        
    def set_password(self, raw_password):
        self.password = make_password(raw_password)

    def check_password(self, raw_password):
        """
        Returns a boolean of whether the raw_password was correct. Handles
        hashing formats behind the scenes.
        """
        def setter(raw_password):
            self.set_password(raw_password)
            self.save()
        return check_password(raw_password, self.password, setter)

    def set_unusable_password(self):
        # Sets a value that will never be a valid hash
        self.password = make_password(None)

    def has_usable_password(self):
        return is_password_usable(self.password)

    def as_list_item(self,ar):
        return E.li(E.strong(self.username),' : ',
          unicode(self),', ',
          unicode(self.profile),', ',
          E.strong(babel.LANGUAGE_DICT.get(self.language)))
      
    @classmethod
    def get_by_username(cls,username,default=models.NOT_PROVIDED):
        """
        `User.get_by_username(x)` is equivalent to
        `User.objects.get(username=x)` except that the text 
        of the DoesNotExist exception is more useful.
        """
        try:
            return cls.objects.get(username=username)
        except cls.DoesNotExist,e:
            if default is models.NOT_PROVIDED:
                raise cls.DoesNotExist(
                  "No %s with username %r" % (
                      unicode(cls._meta.verbose_name),username))
            return default

        


class UserDetail(dd.FormLayout):
  
    box1 = """
    username id profile 
    first_name last_name partner
    email language 
    created modified
    """

    main = """
    box1
    remarks AuthoritiesGiven 
    """
    
class UserInsert(dd.FormLayout):
  
    window_size = (60,'auto')
    
    main = """
    username email 
    first_name last_name
    partner
    language profile     
    """
    
 
class Users(dd.Table):
    """
    Shows the list of all users on this site.
    """
    #~ debug_actions  = True
    required = dict(user_level='manager')
    model = User
    #~ order_by = "last_name first_name".split()
    order_by = ["username"]
    #~ column_names = 'username first_name last_name is_active is_staff is_expert is_superuser *'
    column_names = 'username profile first_name last_name *'
    detail_layout = UserDetail()
    insert_layout = UserInsert()

    #~ @classmethod
    #~ def get_row_permission(cls,action,user,obj):
        #~ """
        #~ Only system managers may edit other users.
        #~ See also :meth:`User.disabled_fields`.
        #~ """
        #~ if not super(Users,cls).get_row_permission(action,user,obj):
            #~ return False
        #~ if user.level >= UserLevel.manager: return True
        #~ if action.readonly: return True
        #~ if user is not None and user == obj: return True
        #~ return False
          
class MySettings(Users):
    use_as_default_table = False
    hide_top_toolbar = True
    #~ model = User
    #~ detail_layout = Users.detail_layout
    #~ detail_layout = UserDetail()
    required = dict()
    default_list_action_name = 'detail'
    
    @classmethod
    def get_default_action(cls):
        #~ return cls.default_elem_action_name
        return actions.ShowDetailAction()
    
class UsersOverview(Users):
    """
    >> from django.conf import settings
    >> settings.LINO.startup()
    >> print settings.LINO.modules.users.UsersOverview.to_rst()
    
    
    $ python manage.py shell
    >> from lino.modlib.users.models import UsersOverview as T; print T.to_rst()
    
    """
    column_names = 'username profile language'
    exclude = dict(profile='')

if settings.LINO.user_model:
  
    class Authority(mixins.UserAuthored):
        """
        An Authority is when a User gives another User the right to "represent him"
       
        :user: points to the user who gives the right of representation. author of this Authority
        :authorized: points to the user who gets the right to represent the author
        
        """
        
        class Meta:
            verbose_name = _("Authority")
            verbose_name_plural = _("Authorities")
            
        #~ quick_search_fields = ('user__username','user__first_name','user__last_name')
        
        authorized = models.ForeignKey(settings.LINO.user_model,
            help_text=_("""\
The user who gets authority to act in your name."""))



        @dd.chooser()
        def authorized_choices(cls,user):
            qs = settings.LINO.user_model.objects.exclude(
                profile=None)
                #~ profile=dd.UserProfiles.blank_item) 20120829
            if user is not None:
                qs = qs.exclude(id=user.id)
                #~ .exclude(level__gte=UserLevels.admin)
            return qs
        
            
    class Authorities(dd.Table):
        required = dict(user_level='manager')
        model = Authority


    class AuthoritiesGiven(Authorities):
        required = dict()
        master_key = 'user'
        label = _("Authorities given")
        column_names = 'authorized'

    class AuthoritiesTaken(Authorities):
        required = dict()
        master_key = 'authorized'
        label = _("Authorities taken")
        column_names = 'user'


