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"),
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'):
url = reverse('admin:payments_transactionprocess_changelist')
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.contrib import admin, messages
from django.contrib.admin.util import unquote
from django.contrib.auth import admin as auth
from django.http import HttpResponseRedirect
from django.utils.safestring import mark_safe
from django.utils.six.moves.urllib.parse import parse_qsl
@ -17,8 +18,8 @@ from .forms import AccountCreationForm, AccountChangeForm
from .models import Account
class AccountAdmin(ExtendedModelAdmin):
list_display = ('name', 'user_link', 'type', 'is_active')
class AccountAdmin(auth.UserAdmin, ExtendedModelAdmin):
list_display = ('name', 'type', 'is_active')
list_filter = (
'type', 'is_active', HasMainUserListFilter
)
@ -32,23 +33,21 @@ class AccountAdmin(ExtendedModelAdmin):
)
fieldsets = (
(_("User"), {
'fields': ('user_link', 'password',),
'fields': ('username', 'password',),
}),
(_("Account info"), {
'fields': (('type', 'language'), 'comments'),
}),
)
readonly_fields = ('user_link',)
search_fields = ('users__username',)
search_fields = ('username',)
add_form = AccountCreationForm
form = AccountChangeForm
filter_horizontal = ()
change_form_template = 'admin/accounts/account/change_form.html'
user_link = admin_link('user', order='user__username')
def name(self, account):
return account.name
name.admin_order_field = 'user__username'
name.admin_order_field = 'username'
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
@ -75,20 +74,10 @@ class AccountAdmin(ExtendedModelAdmin):
return super(AccountAdmin, self).change_view(request, object_id,
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):
""" Select related for performance """
qs = super(AccountAdmin, self).get_queryset(request)
related = ('user', 'invoicecontact')
related = ('invoicecontact',)
return qs.select_related(*related)
@ -97,10 +86,10 @@ admin.site.register(Account, AccountAdmin)
class AccountListAdmin(AccountAdmin):
""" Account list to allow account selection when creating new services """
list_display = ('select_account', 'type', 'user')
list_display = ('select_account', 'type', 'username')
actions = None
search_fields = ['user__username',]
ordering = ('user__username',)
search_fields = ['username',]
ordering = ('username',)
def select_account(self, instance):
# 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
select_account.short_description = _("account")
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):
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))
account_link.short_description = _("account")
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):
""" provide account for filter_by_account_fields """
@ -150,13 +139,13 @@ class AccountAdminMixin(object):
def get_queryset(self, request):
""" Select related for performance """
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):
""" Improve performance of account field and filter by account """
if db_field.name == 'account':
qs = kwargs.get('queryset', db_field.rel.to.objects)
kwargs['queryset'] = qs.select_related('user')
""" Filter by account """
# if db_field.name == 'account':
# qs = kwargs.get('queryset', db_field.rel.to.objects)
# kwargs['queryset'] = qs.select_related('user')
formfield = super(AccountAdminMixin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name in self.filter_by_account_fields:
if hasattr(self, 'account'):

View file

@ -9,17 +9,17 @@ from .serializers import AccountSerializer
class AccountApiMixin(object):
def get_queryset(self):
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):
model = Account
serializer_class = AccountSerializer
singleton_pk = lambda _,request: request.user.account.pk
singleton_pk = lambda _,request: request.user.pk
def get_queryset(self):
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)

View file

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

View file

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

View file

@ -7,6 +7,7 @@ from orchestra.apps.accounts.models import Account
class Command(createsuperuser.Command):
def handle(self, *args, **options):
super(Command, self).handle(*args, **options)
raise NotImplementedError
users = get_user_model().objects.filter()
if len(users) == 1 and not Account.objects.all().exists():
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.core import validators
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services
@ -8,10 +11,11 @@ from orchestra.utils import send_email_template
from . import settings
class Account(models.Model):
# Users depends on Accounts (think about what should happen when you delete an account)
user = models.OneToOneField(djsettings.AUTH_USER_MODEL,
verbose_name=_("user"), related_name='accounts', null=True)
class Account(auth.AbstractBaseUser):
username = models.CharField(_("username"), max_length=64, unique=True,
help_text=_("Required. 30 characters or fewer. Letters, digits and ./-/_ only."),
validators=[validators.RegexValidator(r'^[\w.-]+$',
_("Enter a valid username."), 'invalid')])
type = models.CharField(_("type"), choices=settings.ACCOUNTS_TYPES,
max_length=32, default=settings.ACCOUNTS_DEFAULT_TYPE)
language = models.CharField(_("language"), max_length=2,
@ -19,24 +23,92 @@ class Account(models.Model):
default=settings.ACCOUNTS_DEFAULT_LANGUAGE)
registered_on = models.DateField(_("registered"), auto_now_add=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):
return self.name
@property
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
def get_main(cls):
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):
contacts = self.contacts.filter(email_usages=contacts)
email_to = contacts.values_list('email', flat=True)
send_email_template(template, context, email_to, html=html,
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)

View file

@ -7,7 +7,7 @@ class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Account
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):
list_display = ('name', 'type', 'account_link')
list_filter = ('type',)
search_fields = ['name', 'account__user__username']
search_fields = ['name', 'account__username']
inlines = [UserInline]
add_inlines = []
change_readonly_fields = ('name', 'type')
@ -102,7 +102,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
class DatabaseUserAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = ('username', 'type', 'account_link')
list_filter = ('type',)
search_fields = ['username', 'account__user__username']
search_fields = ['username', 'account__username']
form = DatabaseUserChangeForm
add_form = DatabaseUserCreationForm
change_readonly_fields = ('username', 'type')

View file

@ -1,3 +1,5 @@
import re
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
@ -52,7 +54,7 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin
inlines = [RecordInline, DomainInline]
list_filter = [TopDomainListFilter]
change_readonly_fields = ('name',)
search_fields = ['name', 'account__user__username']
search_fields = ['name', 'account__username']
default_changelist_filters = (
('top_domain', 'True'),
)
@ -91,9 +93,12 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin
qs = super(DomainAdmin, self).get_queryset(request)
qs = qs.select_related('top')
# 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(
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')
if apps.isinstalled('orchestra.apps.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):
# 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:
return self.confirmation(request)
self.options['related_queryset'] = related

View file

@ -73,7 +73,7 @@ class TransactionAdmin(ChangeViewActionsMixin, AccountAdminMixin, admin.ModelAdm
def get_queryset(self, 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):
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):
order_model = get_model(settings.SERVICES_ORDER_MODEL)
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)

View file

@ -2,7 +2,6 @@ from django.conf.urls import patterns, url
from django.core.urlresolvers import reverse
from django.contrib import admin
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 orchestra.admin import ExtendedModelAdmin
@ -14,22 +13,16 @@ from .models import User
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_filter = ('is_staff', 'is_superuser', 'is_active')
list_filter = ('is_active',)
fieldsets = (
(None, {
'fields': ('account', 'username', 'password')
'fields': ('account', 'username', 'password', 'is_active')
}),
(_("Personal info"), {
'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 = (
(None, {
@ -37,7 +30,7 @@ class UserAdmin(AccountAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
'fields': ('username', 'password1', 'password2', 'account'),
}),
)
search_fields = ['username', 'account__user__username']
search_fields = ['username', 'account__username']
readonly_fields = ('display_is_main', 'account_link')
change_readonly_fields = ('username',)
filter_horizontal = ()
@ -101,10 +94,10 @@ class UserAdmin(AccountAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
kwargs['extra_context'] = extra_context
return super(UserAdmin, self).change_view(request, object_id, **kwargs)
def get_queryset(self, request):
""" Select related for performance """
related = ['account__user'] + [ role.name for role in self.roles ]
return super(UserAdmin, self).get_queryset(request).select_related(*related)
# def get_queryset(self, request):
# """ Select related for performance """
# related = ['account'] + [ role.name for role in self.roles ]
# return super(UserAdmin, self).get_queryset(request).select_related(*related)
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.mail import send_mail
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services
class User(auth.AbstractBaseUser):
class User(models.Model):
username = models.CharField(_("username"), max_length=64, unique=True,
help_text=_("Required. 30 characters or fewer. Letters, digits and "
"./-/_ only."),
validators=[validators.RegexValidator(r'^[\w.-]+$',
_("Enter a valid username."), 'invalid')])
password = models.CharField(_("password"), max_length=128)
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
related_name='users')
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_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,
help_text=_("Designates whether this user 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']
help_text=_("Designates whether this account should be treated as active. "
"Unselect this instead of deleting accounts."))
@property
def is_main(self):
return self.account.user == self
return self.username == self.account.username
def get_full_name(self):
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 """
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):
""" Sends an email to this User """
send_mail(subject, message, from_email, [self.email], **kwargs)
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 set_password(self, raw_password):
self.password = make_password(raw_password)
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
object is passed, it checks if the user has all required perms for this
object.
Returns a boolean of whether the raw_password was correct. Handles
hashing formats behind the scenes.
"""
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)
def setter(raw_password):
self.set_password(raw_password)
self.save(update_fields=["password"])
services.register(User, menu=False)
services.register(User)

View file

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