Account-centric alternative user model initial implementation

This commit is contained in:
Marc 2014-09-29 13:34:38 +00:00
parent 6a52e99d10
commit 833b527361
40 changed files with 154 additions and 891 deletions

View file

@ -44,9 +44,6 @@ def get_accounts():
items.MenuItem(_("Accounts"), items.MenuItem(_("Accounts"),
reverse('admin:accounts_account_changelist')) reverse('admin:accounts_account_changelist'))
] ]
if isinstalled('orchestra.apps.users'):
url = reverse('admin:users_user_changelist')
childrens.append(items.MenuItem(_("Users"), url))
if isinstalled('orchestra.apps.payments'): if isinstalled('orchestra.apps.payments'):
url = reverse('admin:payments_transactionprocess_changelist') url = reverse('admin:payments_transactionprocess_changelist')
childrens.append(items.MenuItem(_("Transaction processes"), url)) childrens.append(items.MenuItem(_("Transaction processes"), url))

View file

@ -2,6 +2,7 @@ from django import forms
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.contrib import admin, messages from django.contrib import admin, messages
from django.contrib.admin.util import unquote from django.contrib.admin.util import unquote
from django.contrib.auth import admin as auth
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.six.moves.urllib.parse import parse_qsl from django.utils.six.moves.urllib.parse import parse_qsl
@ -17,8 +18,8 @@ from .forms import AccountCreationForm, AccountChangeForm
from .models import Account from .models import Account
class AccountAdmin(ExtendedModelAdmin): class AccountAdmin(auth.UserAdmin, ExtendedModelAdmin):
list_display = ('name', 'user_link', 'type', 'is_active') list_display = ('name', 'type', 'is_active')
list_filter = ( list_filter = (
'type', 'is_active', HasMainUserListFilter 'type', 'is_active', HasMainUserListFilter
) )
@ -32,23 +33,21 @@ class AccountAdmin(ExtendedModelAdmin):
) )
fieldsets = ( fieldsets = (
(_("User"), { (_("User"), {
'fields': ('user_link', 'password',), 'fields': ('username', 'password',),
}), }),
(_("Account info"), { (_("Account info"), {
'fields': (('type', 'language'), 'comments'), 'fields': (('type', 'language'), 'comments'),
}), }),
) )
readonly_fields = ('user_link',) search_fields = ('username',)
search_fields = ('users__username',)
add_form = AccountCreationForm add_form = AccountCreationForm
form = AccountChangeForm form = AccountChangeForm
filter_horizontal = ()
change_form_template = 'admin/accounts/account/change_form.html' change_form_template = 'admin/accounts/account/change_form.html'
user_link = admin_link('user', order='user__username')
def name(self, account): def name(self, account):
return account.name return account.name
name.admin_order_field = 'user__username' name.admin_order_field = 'username'
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """ """ Make value input widget bigger """
@ -75,20 +74,10 @@ class AccountAdmin(ExtendedModelAdmin):
return super(AccountAdmin, self).change_view(request, object_id, return super(AccountAdmin, self).change_view(request, object_id,
form_url=form_url, extra_context=context) form_url=form_url, extra_context=context)
def save_model(self, request, obj, form, change):
""" Save user and account, they are interdependent """
if change:
return super(AccountAdmin, self).save_model(request, obj, form, change)
obj.user.save()
obj.user_id = obj.user.pk
obj.save()
obj.user.account = obj
obj.user.save()
def get_queryset(self, request): def get_queryset(self, request):
""" Select related for performance """ """ Select related for performance """
qs = super(AccountAdmin, self).get_queryset(request) qs = super(AccountAdmin, self).get_queryset(request)
related = ('user', 'invoicecontact') related = ('invoicecontact',)
return qs.select_related(*related) return qs.select_related(*related)
@ -97,10 +86,10 @@ admin.site.register(Account, AccountAdmin)
class AccountListAdmin(AccountAdmin): class AccountListAdmin(AccountAdmin):
""" Account list to allow account selection when creating new services """ """ Account list to allow account selection when creating new services """
list_display = ('select_account', 'type', 'user') list_display = ('select_account', 'type', 'username')
actions = None actions = None
search_fields = ['user__username',] search_fields = ['username',]
ordering = ('user__username',) ordering = ('username',)
def select_account(self, instance): def select_account(self, instance):
# TODO get query string from request.META['QUERY_STRING'] to preserve filters # TODO get query string from request.META['QUERY_STRING'] to preserve filters
@ -111,7 +100,7 @@ class AccountListAdmin(AccountAdmin):
return '<a href="%(url)s">%(name)s</a>' % context return '<a href="%(url)s">%(name)s</a>' % context
select_account.short_description = _("account") select_account.short_description = _("account")
select_account.allow_tags = True select_account.allow_tags = True
select_account.order_admin_field = 'user__username' select_account.order_admin_field = 'username'
def changelist_view(self, request, extra_context=None): def changelist_view(self, request, extra_context=None):
original_app_label = request.META['PATH_INFO'].split('/')[-5] original_app_label = request.META['PATH_INFO'].split('/')[-5]
@ -139,7 +128,7 @@ class AccountAdminMixin(object):
return '<a href="%s">%s</a>' % (url, str(account)) return '<a href="%s">%s</a>' % (url, str(account))
account_link.short_description = _("account") account_link.short_description = _("account")
account_link.allow_tags = True account_link.allow_tags = True
account_link.admin_order_field = 'account__user__username' account_link.admin_order_field = 'account__username'
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
""" provide account for filter_by_account_fields """ """ provide account for filter_by_account_fields """
@ -150,13 +139,13 @@ class AccountAdminMixin(object):
def get_queryset(self, request): def get_queryset(self, request):
""" Select related for performance """ """ Select related for performance """
qs = super(AccountAdminMixin, self).get_queryset(request) qs = super(AccountAdminMixin, self).get_queryset(request)
return qs.select_related('account__user') return qs.select_related('account')
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Improve performance of account field and filter by account """ """ Filter by account """
if db_field.name == 'account': # if db_field.name == 'account':
qs = kwargs.get('queryset', db_field.rel.to.objects) # qs = kwargs.get('queryset', db_field.rel.to.objects)
kwargs['queryset'] = qs.select_related('user') # kwargs['queryset'] = qs.select_related('user')
formfield = super(AccountAdminMixin, self).formfield_for_dbfield(db_field, **kwargs) formfield = super(AccountAdminMixin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name in self.filter_by_account_fields: if db_field.name in self.filter_by_account_fields:
if hasattr(self, 'account'): if hasattr(self, 'account'):

View file

@ -9,17 +9,17 @@ from .serializers import AccountSerializer
class AccountApiMixin(object): class AccountApiMixin(object):
def get_queryset(self): def get_queryset(self):
qs = super(AccountApiMixin, self).get_queryset() qs = super(AccountApiMixin, self).get_queryset()
return qs.filter(account=self.request.user.account_id) return qs.filter(account=self.request.user.pk)
class AccountViewSet(viewsets.ModelViewSet): class AccountViewSet(viewsets.ModelViewSet):
model = Account model = Account
serializer_class = AccountSerializer serializer_class = AccountSerializer
singleton_pk = lambda _,request: request.user.account.pk singleton_pk = lambda _,request: request.user.pk
def get_queryset(self): def get_queryset(self):
qs = super(AccountViewSet, self).get_queryset() qs = super(AccountViewSet, self).get_queryset()
return qs.filter(id=self.request.user.account_id) return qs.filter(id=self.request.user)
router.register(r'accounts', AccountViewSet) router.register(r'accounts', AccountViewSet)

View file

@ -23,16 +23,12 @@ class AccountCreationForm(auth.forms.UserCreationForm):
return username return username
raise forms.ValidationError(self.error_messages['duplicate_username']) raise forms.ValidationError(self.error_messages['duplicate_username'])
def save(self, commit=True): # def save(self, commit=True):
account = super(auth.forms.UserCreationForm, self).save(commit=False) # account = super(auth.forms.UserCreationForm, self).save(commit=False)
user = User(username=self.cleaned_data['username'], is_admin=True) # account.set_password(self.cleaned_data['password1'])
user.set_password(self.cleaned_data['password1']) # if commit:
user.account = account # account.save()
account.user = user # return account
if commit:
user.save()
account.save()
return account
class AccountChangeForm(forms.ModelForm): class AccountChangeForm(forms.ModelForm):
@ -45,8 +41,8 @@ class AccountChangeForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(AccountChangeForm, self).__init__(*args, **kwargs) super(AccountChangeForm, self).__init__(*args, **kwargs)
account = kwargs.get('instance') account = kwargs.get('instance')
self.fields['username'].widget = ReadOnlyWidget(account.user.username) self.fields['username'].widget = ReadOnlyWidget(account.username)
self.fields['password'].initial = account.user.password self.fields['password'].initial = account.password
def clean_password(self): def clean_password(self):
# Regardless of what the user provides, return the initial value. # Regardless of what the user provides, return the initial value.

View file

@ -18,7 +18,7 @@ class Command(BaseCommand):
) )
option_list = BaseCommand.option_list option_list = BaseCommand.option_list
help = 'Used to create an initial account and its user.' help = 'Used to create an initial account.'
@transaction.atomic @transaction.atomic
def handle(self, *args, **options): def handle(self, *args, **options):
@ -27,5 +27,4 @@ class Command(BaseCommand):
email = options.get('email') email = options.get('email')
username = options.get('username') username = options.get('username')
password = options.get('password') password = options.get('password')
account = Account.objects.create() Account.objects.create_user(username, email=email, password=password)
account.users.create_superuser(username, email, password, is_main=True)

View file

@ -7,6 +7,7 @@ from orchestra.apps.accounts.models import Account
class Command(createsuperuser.Command): class Command(createsuperuser.Command):
def handle(self, *args, **options): def handle(self, *args, **options):
super(Command, self).handle(*args, **options) super(Command, self).handle(*args, **options)
raise NotImplementedError
users = get_user_model().objects.filter() users = get_user_model().objects.filter()
if len(users) == 1 and not Account.objects.all().exists(): if len(users) == 1 and not Account.objects.all().exists():
user = users[0] user = users[0]

View file

@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('type', models.CharField(default=b'INDIVIDUAL', max_length=32, verbose_name='type', choices=[(b'INDIVIDUAL', 'Individual'), (b'ASSOCIATION', 'Association'), (b'CUSTOMER', 'Customer'), (b'COMPANY', 'Company'), (b'PUBLICBODY', 'Public body')])),
('language', models.CharField(default=b'en', max_length=2, verbose_name='language', choices=[(b'en', 'English')])),
('register_date', models.DateTimeField(auto_now_add=True, verbose_name='register date')),
('comments', models.TextField(max_length=256, verbose_name='comments', blank=True)),
('is_active', models.BooleanField(default=True)),
('user', models.OneToOneField(related_name=b'accounts', verbose_name='user', to=settings.AUTH_USER_MODEL)),
],
options={
},
bases=(models.Model,),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='account',
name='user',
field=models.OneToOneField(related_name=b'accounts', null=True, verbose_name='user', to=settings.AUTH_USER_MODEL),
),
]

View file

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_auto_20140909_1850'),
]
operations = [
migrations.RemoveField(
model_name='account',
name='register_date',
),
migrations.AddField(
model_name='account',
name='registered_on',
field=models.DateField(default=datetime.datetime(2014, 9, 26, 13, 25, 49, 42008), verbose_name='registered', auto_now_add=True),
preserve_default=False,
),
]

View file

@ -1,5 +1,8 @@
from django.contrib.auth import models as auth
from django.conf import settings as djsettings from django.conf import settings as djsettings
from django.core import validators
from django.db import models from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.core import services from orchestra.core import services
@ -8,10 +11,11 @@ from orchestra.utils import send_email_template
from . import settings from . import settings
class Account(models.Model): class Account(auth.AbstractBaseUser):
# Users depends on Accounts (think about what should happen when you delete an account) username = models.CharField(_("username"), max_length=64, unique=True,
user = models.OneToOneField(djsettings.AUTH_USER_MODEL, help_text=_("Required. 30 characters or fewer. Letters, digits and ./-/_ only."),
verbose_name=_("user"), related_name='accounts', null=True) validators=[validators.RegexValidator(r'^[\w.-]+$',
_("Enter a valid username."), 'invalid')])
type = models.CharField(_("type"), choices=settings.ACCOUNTS_TYPES, type = models.CharField(_("type"), choices=settings.ACCOUNTS_TYPES,
max_length=32, default=settings.ACCOUNTS_DEFAULT_TYPE) max_length=32, default=settings.ACCOUNTS_DEFAULT_TYPE)
language = models.CharField(_("language"), max_length=2, language = models.CharField(_("language"), max_length=2,
@ -19,24 +23,92 @@ class Account(models.Model):
default=settings.ACCOUNTS_DEFAULT_LANGUAGE) default=settings.ACCOUNTS_DEFAULT_LANGUAGE)
registered_on = models.DateField(_("registered"), auto_now_add=True) registered_on = models.DateField(_("registered"), auto_now_add=True)
comments = models.TextField(_("comments"), max_length=256, blank=True) comments = models.TextField(_("comments"), max_length=256, blank=True)
is_active = models.BooleanField(default=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(_('email address'), blank=True)
is_active = models.BooleanField(_("active"), default=True,
help_text=_("Designates whether this account should be treated as active. "
"Unselect this instead of deleting accounts."))
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
objects = auth.UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@property @property
def name(self): def name(self):
return self.user.username if self.user_id else str(self.pk) return self.username
@property
def is_superuser(self):
return self.pk == settings.ACCOUNTS_MAIN_PK
@property
def is_staff(self):
return self.is_superuser
@classmethod @classmethod
def get_main(cls): def get_main(cls):
return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK) return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK)
def save(self, *args, **kwargs):
created = not self.pk
super(Account, self).save(*args, **kwargs)
if created:
self.users.create(username=self.username, password=self.password)
def send_email(self, template, context, contacts=[], attachments=[], html=None): def send_email(self, template, context, contacts=[], attachments=[], html=None):
contacts = self.contacts.filter(email_usages=contacts) contacts = self.contacts.filter(email_usages=contacts)
email_to = contacts.values_list('email', flat=True) email_to = contacts.values_list('email', flat=True)
send_email_template(template, context, email_to, html=html, send_email_template(template, context, email_to, html=html,
attachments=attachments) attachments=attachments)
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip() or self.username
def get_short_name(self):
""" Returns the short name for the user """
return self.first_name
def has_perm(self, perm, obj=None):
"""
Returns True if the user has the specified permission. This method
queries all available auth backends, but returns immediately if any
backend returns True. Thus, a user who has permission from a single
auth backend is assumed to have permission in general. If an object is
provided, permissions for this specific object are checked.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
return auth._user_has_perm(self, perm, obj)
def has_perms(self, perm_list, obj=None):
"""
Returns True if the user has each of the specified permissions. If
object is passed, it checks if the user has all required perms for this
object.
"""
for perm in perm_list:
if not self.has_perm(perm, obj):
return False
return True
def has_module_perms(self, app_label):
"""
Returns True if the user has any permissions in the given app label.
Uses pretty much the same logic as has_perm, above.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
return auth._user_has_module_perms(self, app_label)
services.register(Account, menu=False) services.register(Account, menu=False)

View file

@ -7,7 +7,7 @@ class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = Account model = Account
fields = ( fields = (
'url', 'user', 'type', 'language', 'register_date', 'is_active' 'url', 'username', 'type', 'language', 'register_date', 'is_active'
) )

View file

@ -1,126 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '__first__'),
]
operations = [
migrations.CreateModel(
name='Bill',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('number', models.CharField(unique=True, max_length=16, verbose_name='number', blank=True)),
('type', models.CharField(max_length=16, verbose_name='type', choices=[(b'INVOICE', 'Invoice'), (b'AMENDMENTINVOICE', 'Amendment invoice'), (b'FEE', 'Fee'), (b'AMENDMENTFEE', 'Amendment Fee'), (b'BUDGET', 'Budget')])),
('status', models.CharField(default=b'OPEN', max_length=16, verbose_name='status', choices=[(b'OPEN', 'Open'), (b'CLOSED', 'Closed'), (b'SENT', 'Sent'), (b'PAID', 'Paid'), (b'BAD_DEBT', 'Bad debt')])),
('created_on', models.DateTimeField(auto_now_add=True, verbose_name='created on')),
('due_on', models.DateField(null=True, verbose_name='due on', blank=True)),
('last_modified_on', models.DateTimeField(auto_now=True, verbose_name='last modified on')),
('comments', models.TextField(verbose_name='comments', blank=True)),
('html', models.TextField(verbose_name='HTML', blank=True)),
('account', models.ForeignKey(related_name=b'bill', verbose_name='account', to='accounts.Account')),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='BillLine',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('description', models.CharField(max_length=256, verbose_name='description')),
('rate', models.DecimalField(null=True, verbose_name='rate', max_digits=12, decimal_places=2, blank=True)),
('amount', models.DecimalField(verbose_name='amount', max_digits=12, decimal_places=2)),
('total', models.DecimalField(verbose_name='total', max_digits=12, decimal_places=2)),
('tax', models.PositiveIntegerField(verbose_name='tax')),
('order_id', models.PositiveIntegerField(null=True, blank=True)),
('order_last_bill_date', models.DateTimeField(null=True)),
('order_billed_until', models.DateTimeField(null=True)),
('auto', models.BooleanField(default=False)),
('amended_line', models.ForeignKey(related_name=b'amendment_lines', verbose_name='amended line', blank=True, to='bills.BillLine', null=True)),
('bill', models.ForeignKey(related_name=b'billlines', verbose_name='bill', to='bills.Bill')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='BillSubline',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('description', models.CharField(max_length=256, verbose_name='description')),
('total', models.DecimalField(max_digits=12, decimal_places=2)),
('bill_line', models.ForeignKey(related_name=b'sublines', verbose_name='bill line', to='bills.BillLine')),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='BudgetLine',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('description', models.CharField(max_length=256, verbose_name='description')),
('rate', models.DecimalField(null=True, verbose_name='rate', max_digits=12, decimal_places=2, blank=True)),
('amount', models.DecimalField(verbose_name='amount', max_digits=12, decimal_places=2)),
('total', models.DecimalField(verbose_name='total', max_digits=12, decimal_places=2)),
('tax', models.PositiveIntegerField(verbose_name='tax')),
('bill', models.ForeignKey(related_name=b'budgetlines', verbose_name='bill', to='bills.Bill')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='AmendmentFee',
fields=[
],
options={
'proxy': True,
},
bases=('bills.bill',),
),
migrations.CreateModel(
name='AmendmentInvoice',
fields=[
],
options={
'proxy': True,
},
bases=('bills.bill',),
),
migrations.CreateModel(
name='Budget',
fields=[
],
options={
'proxy': True,
},
bases=('bills.bill',),
),
migrations.CreateModel(
name='Fee',
fields=[
],
options={
'proxy': True,
},
bases=('bills.bill',),
),
migrations.CreateModel(
name='Invoice',
fields=[
],
options={
'proxy': True,
},
bases=('bills.bill',),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='bill',
name='closed_on',
field=models.DateTimeField(null=True, verbose_name='closed on', blank=True),
preserve_default=True,
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0002_bill_closed_on'),
]
operations = [
migrations.AddField(
model_name='bill',
name='total',
field=models.DecimalField(default=10, max_digits=12, decimal_places=2),
preserve_default=False,
),
]

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0003_bill_total'),
]
operations = [
migrations.AlterField(
model_name='bill',
name='total',
field=models.DecimalField(default=0, max_digits=12, decimal_places=2),
),
]

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0004_auto_20140911_1234'),
]
operations = [
migrations.RenameField(
model_name='billsubline',
old_name='bill_line',
new_name='line',
),
]

View file

@ -1,59 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0005_auto_20140911_1234'),
]
operations = [
migrations.RemoveField(
model_name='budgetline',
name='bill',
),
migrations.DeleteModel(
name='BudgetLine',
),
migrations.DeleteModel(
name='Budget',
),
migrations.CreateModel(
name='ProForma',
fields=[
],
options={
'proxy': True,
},
bases=('bills.bill',),
),
migrations.RemoveField(
model_name='billline',
name='auto',
),
migrations.RemoveField(
model_name='billline',
name='order_billed_until',
),
migrations.RemoveField(
model_name='billline',
name='order_id',
),
migrations.RemoveField(
model_name='billline',
name='order_last_bill_date',
),
migrations.AlterField(
model_name='bill',
name='type',
field=models.CharField(max_length=16, verbose_name='type', choices=[(b'INVOICE', 'Invoice'), (b'AMENDMENTINVOICE', 'Amendment invoice'), (b'FEE', 'Fee'), (b'AMENDMENTFEE', 'Amendment Fee'), (b'PROFORMA', 'Pro forma')]),
),
migrations.AlterField(
model_name='billline',
name='bill',
field=models.ForeignKey(related_name=b'lines', verbose_name='bill', to='bills.Bill'),
),
]

View file

@ -1,50 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0006_auto_20140911_1238'),
]
operations = [
migrations.RemoveField(
model_name='bill',
name='status',
),
migrations.RemoveField(
model_name='billline',
name='amount',
),
migrations.RemoveField(
model_name='billline',
name='total',
),
migrations.AddField(
model_name='bill',
name='is_open',
field=models.BooleanField(default=True, verbose_name='is open'),
preserve_default=True,
),
migrations.AddField(
model_name='bill',
name='is_sent',
field=models.BooleanField(default=False, verbose_name='is sent'),
preserve_default=True,
),
migrations.AddField(
model_name='billline',
name='quantity',
field=models.DecimalField(default=10, verbose_name='quantity', max_digits=12, decimal_places=2),
preserve_default=False,
),
migrations.AddField(
model_name='billline',
name='subtotal',
field=models.DecimalField(default=20, verbose_name='subtotal', max_digits=12, decimal_places=2),
preserve_default=False,
),
]

View file

@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0007_auto_20140918_1454'),
]
operations = [
migrations.AlterModelOptions(
name='bill',
options={'get_latest_by': 'id'},
),
]

View file

@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('orders', '__first__'),
('bills', '0008_auto_20140926_1218'),
]
operations = [
migrations.AddField(
model_name='billline',
name='created_on',
field=models.DateField(default=datetime.datetime(2014, 9, 26, 12, 20, 24, 908200), verbose_name='created', auto_now_add=True),
preserve_default=False,
),
migrations.AddField(
model_name='billline',
name='order_billed_on',
field=models.DateField(null=True, verbose_name='order billed', blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='billline',
name='order_billed_until',
field=models.DateField(null=True, verbose_name='order billed until', blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='billline',
name='order_id',
field=models.ForeignKey(blank=True, to='orders.Order', help_text='Informative link back to the order', null=True),
preserve_default=True,
),
migrations.AddField(
model_name='billsubline',
name='type',
field=models.CharField(default=b'OTHER', max_length=16, verbose_name='type', choices=[(b'VOLUME', 'Volume'), (b'COMPENSATION', 'Compensation'), (b'OTHER', 'Other')]),
preserve_default=True,
),
]

View file

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0009_auto_20140926_1220'),
]
operations = [
migrations.AlterField(
model_name='bill',
name='closed_on',
field=models.DateField(null=True, verbose_name='closed on', blank=True),
),
migrations.AlterField(
model_name='bill',
name='created_on',
field=models.DateField(auto_now_add=True, verbose_name='created on'),
),
migrations.AlterField(
model_name='bill',
name='last_modified_on',
field=models.DateField(auto_now=True, verbose_name='last modified on'),
),
]

View file

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('bills', '0010_auto_20140926_1326'),
]
operations = [
migrations.RemoveField(
model_name='bill',
name='last_modified_on',
),
migrations.AddField(
model_name='bill',
name='updated_on',
field=models.DateField(default=datetime.date(2014, 9, 26), verbose_name='updated on', auto_now=True),
preserve_default=False,
),
]

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0011_auto_20140926_1334'),
]
operations = [
migrations.RenameField(
model_name='billline',
old_name='order_id',
new_name='order',
),
]

View file

@ -57,7 +57,7 @@ class PermissionInline(AccountAdminMixin, admin.TabularInline):
class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = ('name', 'type', 'account_link') list_display = ('name', 'type', 'account_link')
list_filter = ('type',) list_filter = ('type',)
search_fields = ['name', 'account__user__username'] search_fields = ['name', 'account__username']
inlines = [UserInline] inlines = [UserInline]
add_inlines = [] add_inlines = []
change_readonly_fields = ('name', 'type') change_readonly_fields = ('name', 'type')
@ -102,7 +102,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
class DatabaseUserAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class DatabaseUserAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = ('username', 'type', 'account_link') list_display = ('username', 'type', 'account_link')
list_filter = ('type',) list_filter = ('type',)
search_fields = ['username', 'account__user__username'] search_fields = ['username', 'account__username']
form = DatabaseUserChangeForm form = DatabaseUserChangeForm
add_form = DatabaseUserCreationForm add_form = DatabaseUserCreationForm
change_readonly_fields = ('username', 'type') change_readonly_fields = ('username', 'type')

View file

@ -1,3 +1,5 @@
import re
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -52,7 +54,7 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin
inlines = [RecordInline, DomainInline] inlines = [RecordInline, DomainInline]
list_filter = [TopDomainListFilter] list_filter = [TopDomainListFilter]
change_readonly_fields = ('name',) change_readonly_fields = ('name',)
search_fields = ['name', 'account__user__username'] search_fields = ['name', 'account__username']
default_changelist_filters = ( default_changelist_filters = (
('top_domain', 'True'), ('top_domain', 'True'),
) )
@ -91,9 +93,12 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin
qs = super(DomainAdmin, self).get_queryset(request) qs = super(DomainAdmin, self).get_queryset(request)
qs = qs.select_related('top') qs = qs.select_related('top')
# For some reason if we do this we know for sure that join table will be called T4 # For some reason if we do this we know for sure that join table will be called T4
str(qs.query) query = str(qs.query)
table = re.findall(r'(T\d+)\."account_id"', query)[0]
qs = qs.extra( qs = qs.extra(
select={'structured_name': 'CONCAT(T4.name, domains_domain.name)'}, select={
'structured_name': 'CONCAT({table}.name, domains_domain.name)'.format(table=table)
},
).order_by('structured_name') ).order_by('structured_name')
if apps.isinstalled('orchestra.apps.websites'): if apps.isinstalled('orchestra.apps.websites'):
qs = qs.prefetch_related('websites') qs = qs.prefetch_related('websites')

View file

@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.core.validators
import orchestra.apps.domains.validators
import orchestra.apps.domains.utils
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_auto_20140909_1850'),
]
operations = [
migrations.CreateModel(
name='Domain',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=256, verbose_name='name', validators=[orchestra.core.validators.validate_hostname, orchestra.apps.domains.validators.validate_allowed_domain])),
('serial', models.IntegerField(default=orchestra.apps.domains.utils.generate_zone_serial, help_text='Serial number', verbose_name='serial')),
('account', models.ForeignKey(related_name=b'domains', verbose_name='Account', blank=True, to='accounts.Account')),
('top', models.ForeignKey(related_name=b'subdomains', to='domains.Domain', null=True)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Record',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('type', models.CharField(max_length=32, verbose_name='type', choices=[(b'MX', b'MX'), (b'NS', b'NS'), (b'CNAME', b'CNAME'), (b'A', 'A (IPv4 address)'), (b'AAAA', 'AAAA (IPv6 address)'), (b'SRV', b'SRV'), (b'TXT', b'TXT'), (b'SOA', b'SOA')])),
('value', models.CharField(max_length=256, verbose_name='value')),
('domain', models.ForeignKey(related_name=b'records', verbose_name='domain', to='domains.Domain')),
],
options={
},
bases=(models.Model,),
),
]

View file

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.apps.domains.validators
class Migration(migrations.Migration):
dependencies = [
('domains', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='record',
name='ttl',
field=models.CharField(default='', validators=[orchestra.apps.domains.validators.validate_zone_interval], max_length=8, blank=True, help_text='Record TTL, defaults to 1h', verbose_name='TTL'),
preserve_default=False,
),
]

View file

@ -54,7 +54,7 @@ class BillSelectedOrders(object):
def select_related(self, request): def select_related(self, request):
# TODO use changelist ? # TODO use changelist ?
related = self.queryset.get_related().select_related('account__user', 'service') related = self.queryset.get_related().select_related('account', 'service')
if not related: if not related:
return self.confirmation(request) return self.confirmation(request)
self.options['related_queryset'] = related self.options['related_queryset'] = related

View file

@ -73,7 +73,7 @@ class TransactionAdmin(ChangeViewActionsMixin, AccountAdminMixin, admin.ModelAdm
def get_queryset(self, request): def get_queryset(self, request):
qs = super(TransactionAdmin, self).get_queryset(request) qs = super(TransactionAdmin, self).get_queryset(request)
return qs.select_related('source', 'bill__account__user') return qs.select_related('source', 'bill__account')
def get_change_view_actions(self, obj=None): def get_change_view_actions(self, obj=None):
actions = super(TransactionAdmin, self).get_change_view_actions() actions = super(TransactionAdmin, self).get_change_view_actions()

View file

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.models.fields
import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('djcelery', '__first__'),
('contenttypes', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='MonitorData',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('monitor', models.CharField(max_length=256, verbose_name='monitor', choices=[(b'Apache2Backend', 'Apache 2'), (b'Apache2Traffic', 'Apache 2 Traffic'), (b'AutoresponseBackend', 'Mail autoresponse'), (b'AwstatsBackend', 'Awstats'), (b'Bind9MasterDomainBackend', 'Bind9 master domain'), (b'Bind9SlaveDomainBackend', 'Bind9 slave domain'), (b'DokuWikiMuBackend', 'DokuWiki multisite'), (b'DrupalMuBackend', 'Drupal multisite'), (b'FTPTraffic', 'FTP traffic'), (b'MailSystemUserBackend', 'Mail system user'), (b'MaildirDisk', 'Maildir disk usage'), (b'MailmanBackend', b'Mailman'), (b'MailmanTraffic', b'MailmanTraffic'), (b'MySQLDBBackend', b'MySQL database'), (b'MySQLPermissionBackend', b'MySQL permission'), (b'MySQLUserBackend', b'MySQL user'), (b'MysqlDisk', 'MySQL disk'), (b'OpenVZTraffic', b'OpenVZTraffic'), (b'PHPFPMBackend', 'PHP-FPM'), (b'PHPFcgidBackend', 'PHP-Fcgid'), (b'PostfixAddressBackend', 'Postfix address'), (b'ServiceController', b'ServiceController'), (b'ServiceMonitor', b'ServiceMonitor'), (b'StaticBackend', 'Static'), (b'SystemUserBackend', 'System User'), (b'SystemUserDisk', 'System user disk'), (b'WebalizerBackend', 'Webalizer'), (b'WordpressMuBackend', 'Wordpress multisite')])),
('object_id', models.PositiveIntegerField()),
('date', models.DateTimeField(auto_now_add=True, verbose_name='date')),
('value', models.DecimalField(verbose_name='value', max_digits=16, decimal_places=2)),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
],
options={
'get_latest_by': 'id',
'verbose_name_plural': 'monitor data',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Resource',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(help_text='Required. 32 characters or fewer. Lowercase letters, digits and hyphen only.', max_length=32, verbose_name='name', validators=[django.core.validators.RegexValidator(b'^[a-z0-9_\\-]+$', 'Enter a valid name.', b'invalid')])),
('verbose_name', models.CharField(max_length=256, verbose_name='verbose name')),
('period', models.CharField(default=b'LAST', help_text='Operation used for aggregating this resource monitoreddata.', max_length=16, verbose_name='period', choices=[(b'LAST', 'Last'), (b'MONTHLY_SUM', 'Monthly Sum'), (b'MONTHLY_AVG', 'Monthly Average')])),
('ondemand', models.BooleanField(default=False, help_text='If enabled the resource will not be pre-allocated, but allocated under the application demand', verbose_name='on demand')),
('default_allocation', models.PositiveIntegerField(help_text='Default allocation value used when this is not an on demand resource', null=True, verbose_name='default allocation', blank=True)),
('unit', models.CharField(help_text='The unit in which this resource is measured. For example GB, KB or subscribers', max_length=16, verbose_name='unit')),
('scale', models.PositiveIntegerField(help_text='Scale in which this resource monitoring resoults should be prorcessed to match with unit.', verbose_name='scale')),
('disable_trigger', models.BooleanField(default=False, help_text='Disables monitors exeeded and recovery triggers', verbose_name='disable trigger')),
('monitors', orchestra.models.fields.MultiSelectField(blank=True, help_text='Monitor backends used for monitoring this resource.', max_length=256, verbose_name='monitors', choices=[(b'Apache2Backend', 'Apache 2'), (b'Apache2Traffic', 'Apache 2 Traffic'), (b'AutoresponseBackend', 'Mail autoresponse'), (b'AwstatsBackend', 'Awstats'), (b'Bind9MasterDomainBackend', 'Bind9 master domain'), (b'Bind9SlaveDomainBackend', 'Bind9 slave domain'), (b'DokuWikiMuBackend', 'DokuWiki multisite'), (b'DrupalMuBackend', 'Drupal multisite'), (b'FTPTraffic', 'FTP traffic'), (b'MailSystemUserBackend', 'Mail system user'), (b'MaildirDisk', 'Maildir disk usage'), (b'MailmanBackend', b'Mailman'), (b'MailmanTraffic', b'MailmanTraffic'), (b'MySQLDBBackend', b'MySQL database'), (b'MySQLPermissionBackend', b'MySQL permission'), (b'MySQLUserBackend', b'MySQL user'), (b'MysqlDisk', 'MySQL disk'), (b'OpenVZTraffic', b'OpenVZTraffic'), (b'PHPFPMBackend', 'PHP-FPM'), (b'PHPFcgidBackend', 'PHP-Fcgid'), (b'PostfixAddressBackend', 'Postfix address'), (b'ServiceController', b'ServiceController'), (b'ServiceMonitor', b'ServiceMonitor'), (b'StaticBackend', 'Static'), (b'SystemUserBackend', 'System User'), (b'SystemUserDisk', 'System user disk'), (b'WebalizerBackend', 'Webalizer'), (b'WordpressMuBackend', 'Wordpress multisite')])),
('is_active', models.BooleanField(default=True, verbose_name='is active')),
('content_type', models.ForeignKey(help_text='Model where this resource will be hooked.', to='contenttypes.ContentType')),
('crontab', models.ForeignKey(blank=True, to='djcelery.CrontabSchedule', help_text='Crontab for periodic execution. Leave it empty to disable periodic monitoring', null=True, verbose_name='crontab')),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='ResourceData',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('object_id', models.PositiveIntegerField()),
('used', models.PositiveIntegerField(null=True)),
('last_update', models.DateTimeField(null=True)),
('allocated', models.PositiveIntegerField(null=True, blank=True)),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
('resource', models.ForeignKey(related_name=b'dataset', to='resources.Resource')),
],
options={
'verbose_name_plural': 'resource data',
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='resourcedata',
unique_together=set([('resource', 'content_type', 'object_id')]),
),
migrations.AlterUniqueTogether(
name='resource',
unique_together=set([('name', 'content_type'), ('verbose_name', 'content_type')]),
),
]

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('resources', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='resource',
old_name='ondemand',
new_name='on_demand',
),
]

View file

@ -1,70 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('resources', '0002_auto_20140926_1143'),
]
operations = [
migrations.RemoveField(
model_name='monitordata',
name='date',
),
migrations.RemoveField(
model_name='resourcedata',
name='last_update',
),
migrations.AddField(
model_name='monitordata',
name='created_at',
field=models.DateTimeField(default=datetime.datetime(2014, 9, 26, 13, 25, 33, 290000), verbose_name='created', auto_now_add=True),
preserve_default=False,
),
migrations.AddField(
model_name='resourcedata',
name='updated_at',
field=models.DateTimeField(null=True, verbose_name='updated'),
preserve_default=True,
),
migrations.AlterField(
model_name='monitordata',
name='content_type',
field=models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType'),
),
migrations.AlterField(
model_name='monitordata',
name='object_id',
field=models.PositiveIntegerField(verbose_name='object id'),
),
migrations.AlterField(
model_name='resourcedata',
name='allocated',
field=models.PositiveIntegerField(null=True, verbose_name='allocated', blank=True),
),
migrations.AlterField(
model_name='resourcedata',
name='content_type',
field=models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType'),
),
migrations.AlterField(
model_name='resourcedata',
name='object_id',
field=models.PositiveIntegerField(verbose_name='object id'),
),
migrations.AlterField(
model_name='resourcedata',
name='resource',
field=models.ForeignKey(related_name=b'dataset', verbose_name='resource', to='resources.Resource'),
),
migrations.AlterField(
model_name='resourcedata',
name='used',
field=models.PositiveIntegerField(null=True, verbose_name='used'),
),
]

View file

@ -326,7 +326,7 @@ class Service(models.Model):
def update_orders(self): def update_orders(self):
order_model = get_model(settings.SERVICES_ORDER_MODEL) order_model = get_model(settings.SERVICES_ORDER_MODEL)
related_model = self.content_type.model_class() related_model = self.content_type.model_class()
for instance in related_model.objects.all().select_related('account__user'): for instance in related_model.objects.all().select_related('account'):
order_model.update_orders(instance, service=self) order_model.update_orders(instance, service=self)

View file

@ -2,7 +2,6 @@ from django.conf.urls import patterns, url
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib import admin from django.contrib import admin
from django.contrib.admin.util import unquote from django.contrib.admin.util import unquote
from django.contrib.auth import admin as auth
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
@ -14,22 +13,16 @@ from .models import User
from .roles.filters import role_list_filter_factory from .roles.filters import role_list_filter_factory
class UserAdmin(AccountAdminMixin, auth.UserAdmin, ExtendedModelAdmin): class UserAdmin(AccountAdminMixin, ExtendedModelAdmin):
list_display = ('username', 'display_is_main') list_display = ('username', 'display_is_main')
list_filter = ('is_staff', 'is_superuser', 'is_active') list_filter = ('is_active',)
fieldsets = ( fieldsets = (
(None, { (None, {
'fields': ('account', 'username', 'password') 'fields': ('account', 'username', 'password', 'is_active')
}), }),
(_("Personal info"), { (_("Personal info"), {
'fields': ('first_name', 'last_name', 'email') 'fields': ('first_name', 'last_name', 'email')
}), }),
(_("Permissions"), {
'fields': ('is_active', 'is_staff', 'is_superuser', 'display_is_main')
}),
(_("Important dates"), {
'fields': ('last_login', 'date_joined')
}),
) )
add_fieldsets = ( add_fieldsets = (
(None, { (None, {
@ -37,7 +30,7 @@ class UserAdmin(AccountAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
'fields': ('username', 'password1', 'password2', 'account'), 'fields': ('username', 'password1', 'password2', 'account'),
}), }),
) )
search_fields = ['username', 'account__user__username'] search_fields = ['username', 'account__username']
readonly_fields = ('display_is_main', 'account_link') readonly_fields = ('display_is_main', 'account_link')
change_readonly_fields = ('username',) change_readonly_fields = ('username',)
filter_horizontal = () filter_horizontal = ()
@ -101,10 +94,10 @@ class UserAdmin(AccountAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
kwargs['extra_context'] = extra_context kwargs['extra_context'] = extra_context
return super(UserAdmin, self).change_view(request, object_id, **kwargs) return super(UserAdmin, self).change_view(request, object_id, **kwargs)
def get_queryset(self, request): # def get_queryset(self, request):
""" Select related for performance """ # """ Select related for performance """
related = ['account__user'] + [ role.name for role in self.roles ] # related = ['account'] + [ role.name for role in self.roles ]
return super(UserAdmin, self).get_queryset(request).select_related(*related) # return super(UserAdmin, self).get_queryset(request).select_related(*related)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)

View file

@ -1,45 +1,32 @@
from django.contrib.auth import models as auth from django.contrib.auth.hashers import make_password
from django.core import validators from django.core import validators
from django.core.mail import send_mail from django.core.mail import send_mail
from django.db import models from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.core import services from orchestra.core import services
class User(auth.AbstractBaseUser): class User(models.Model):
username = models.CharField(_("username"), max_length=64, unique=True, username = models.CharField(_("username"), max_length=64, unique=True,
help_text=_("Required. 30 characters or fewer. Letters, digits and " help_text=_("Required. 30 characters or fewer. Letters, digits and "
"./-/_ only."), "./-/_ only."),
validators=[validators.RegexValidator(r'^[\w.-]+$', validators=[validators.RegexValidator(r'^[\w.-]+$',
_("Enter a valid username."), 'invalid')]) _("Enter a valid username."), 'invalid')])
password = models.CharField(_("password"), max_length=128)
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
related_name='users') related_name='users')
first_name = models.CharField(_("first name"), max_length=30, blank=True) first_name = models.CharField(_("first name"), max_length=30, blank=True)
last_name = models.CharField(_("last name"), max_length=30, blank=True) last_name = models.CharField(_("last name"), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True) email = models.EmailField(_('email address'), blank=True)
is_superuser = models.BooleanField(_("superuser status"), default=False,
help_text=_("Designates that this user has all permissions without "
"explicitly assigning them."))
is_staff = models.BooleanField(_("staff status"), default=False,
help_text=_("Designates whether the user can log into this admin "
"site."))
is_admin = models.BooleanField(_("admin status"), default=False,
help_text=_("Designates whether the user can administrate its account."))
is_active = models.BooleanField(_("active"), default=True, is_active = models.BooleanField(_("active"), default=True,
help_text=_("Designates whether this user should be treated as " help_text=_("Designates whether this account should be treated as active. "
"active. Unselect this instead of deleting accounts.")) "Unselect this instead of deleting accounts."))
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
objects = auth.UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
@property @property
def is_main(self): def is_main(self):
return self.account.user == self return self.username == self.account.username
def get_full_name(self): def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name) full_name = '%s %s' % (self.first_name, self.last_name)
@ -49,44 +36,24 @@ class User(auth.AbstractBaseUser):
""" Returns the short name for the user """ """ Returns the short name for the user """
return self.first_name return self.first_name
def get_description(self):
return "{full_name}, {email}".format(full_name=self.get_full_name(), email=self.email)
def email_user(self, subject, message, from_email=None, **kwargs): def email_user(self, subject, message, from_email=None, **kwargs):
""" Sends an email to this User """ """ Sends an email to this User """
send_mail(subject, message, from_email, [self.email], **kwargs) send_mail(subject, message, from_email, [self.email], **kwargs)
def has_perm(self, perm, obj=None): def set_password(self, raw_password):
""" self.password = make_password(raw_password)
Returns True if the user has the specified permission. This method
queries all available auth backends, but returns immediately if any
backend returns True. Thus, a user who has permission from a single
auth backend is assumed to have permission in general. If an object is
provided, permissions for this specific object are checked.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
return auth._user_has_perm(self, perm, obj)
def has_perms(self, perm_list, obj=None): def check_password(self, raw_password):
""" """
Returns True if the user has each of the specified permissions. If Returns a boolean of whether the raw_password was correct. Handles
object is passed, it checks if the user has all required perms for this hashing formats behind the scenes.
object.
""" """
for perm in perm_list: def setter(raw_password):
if not self.has_perm(perm, obj): self.set_password(raw_password)
return False self.save(update_fields=["password"])
return True
def has_module_perms(self, app_label):
"""
Returns True if the user has any permissions in the given app label.
Uses pretty much the same logic as has_perm, above.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
return auth._user_has_module_perms(self, app_label)
services.register(User, menu=False) services.register(User)

View file

@ -70,8 +70,8 @@ INSTALLED_APPS = (
'orchestra.apps.domains', 'orchestra.apps.domains',
'orchestra.apps.users', 'orchestra.apps.users',
# 'orchestra.apps.users.roles.mail', # 'orchestra.apps.users.roles.mail',
'orchestra.apps.users.roles.jabber', # 'orchestra.apps.users.roles.jabber',
'orchestra.apps.users.roles.posix', # 'orchestra.apps.users.roles.posix',
'orchestra.apps.mails', 'orchestra.apps.mails',
'orchestra.apps.lists', 'orchestra.apps.lists',
'orchestra.apps.webapps', 'orchestra.apps.webapps',
@ -114,7 +114,7 @@ INSTALLED_APPS = (
) )
AUTH_USER_MODEL = 'users.User' AUTH_USER_MODEL = 'accounts.Account'
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
@ -145,7 +145,6 @@ FLUENT_DASHBOARD_APP_GROUPS = (
'models': ( 'models': (
'orchestra.apps.accounts.models.Account', 'orchestra.apps.accounts.models.Account',
'orchestra.apps.contacts.models.Contact', 'orchestra.apps.contacts.models.Contact',
'orchestra.apps.users.models.User',
'orchestra.apps.orders.models.Order', 'orchestra.apps.orders.models.Order',
'orchestra.apps.services.models.ContractedPlan', 'orchestra.apps.services.models.ContractedPlan',
'orchestra.apps.bills.models.Bill', 'orchestra.apps.bills.models.Bill',