Improvements on bills app
This commit is contained in:
parent
2a37cfc8d7
commit
3ea6fde7bd
14
ROADMAP.md
14
ROADMAP.md
|
@ -12,12 +12,13 @@
|
|||
2. [x] [Orchestra-orm](https://github.com/glic3rinu/orchestra-orm) a Python library for easily interacting with the REST API
|
||||
3. [x] Service orchestration framework
|
||||
4. [ ] Data model, input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and some documentation of:
|
||||
1. [ ] Web applications and FTP accounts
|
||||
1. [x] Web applications
|
||||
2. [ ] FTP accounts
|
||||
2. [ ] Databases
|
||||
1. [ ] Mail accounts, aliases, forwards
|
||||
1. [ ] DNS
|
||||
1. [x] DNS
|
||||
1. [ ] Mailing lists
|
||||
1. [ ] Contact management and service contraction
|
||||
1. [x] Contact management and service contraction
|
||||
1. [ ] Object level permissions system
|
||||
1. [ ] Unittests of all the logic
|
||||
2. [ ] Functional tests of all Admin and REST interations
|
||||
|
@ -26,12 +27,12 @@
|
|||
|
||||
### 1.0b1 Milestone (first beta release on Jul '14)
|
||||
|
||||
1. [ ] Resource monitoring
|
||||
1. [x] Resource monitoring
|
||||
1. [ ] Orders
|
||||
2. [ ] Pricing
|
||||
3. [ ] Billing
|
||||
1. [ ] Payment gateways
|
||||
2. [ ] Scheduling of service cancellations
|
||||
1. [ ] Payment methods
|
||||
2. [ ] Scheduling of service cancellations and deactivations
|
||||
1. [ ] Full documentation
|
||||
|
||||
|
||||
|
@ -41,3 +42,4 @@
|
|||
1. [ ] Integration with third-party service providers, e.g. Gandi
|
||||
1. [ ] Support for additional services like VPS
|
||||
2. [ ] Issue tracking system
|
||||
3. [ ] Translation to Spanish and Catalan
|
||||
|
|
|
@ -42,6 +42,7 @@ class AccountAdmin(ExtendedModelAdmin):
|
|||
search_fields = ('users__username',)
|
||||
add_form = AccountCreationForm
|
||||
form = AccountChangeForm
|
||||
change_form_template = 'admin/accounts/account/change_form.html'
|
||||
|
||||
user_link = admin_link('user', order='user__username')
|
||||
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
{% block object-tools-items %}
|
||||
{% if from_account %}
|
||||
<li>
|
||||
<a href="{% url account_opts|admin_urlname:'change' account.pk|admin_urlquote %}" class="historylink">{{ account|truncatewords:"18" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="./" class="historylink">{% trans 'Show all' %}</a>
|
||||
</li>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -18,6 +19,21 @@ class BillLineInline(admin.TabularInline):
|
|||
'description', 'initial_date', 'final_date', 'price', 'amount', 'tax'
|
||||
)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj and obj.status != Bill.OPEN:
|
||||
return self.fields
|
||||
return super(BillLineInline, self).get_readonly_fields(request, obj=obj)
|
||||
|
||||
def has_add_permission(self, request):
|
||||
if request.__bill__ and request.__bill__.status != Bill.OPEN:
|
||||
return False
|
||||
return super(BillLineInline, self).has_add_permission(request)
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
if obj and obj.status != Bill.OPEN:
|
||||
return False
|
||||
return super(BillLineInline, self).has_delete_permission(request, obj=obj)
|
||||
|
||||
|
||||
class BudgetLineInline(admin.TabularInline):
|
||||
model = Budget
|
||||
|
@ -28,15 +44,25 @@ class BudgetLineInline(admin.TabularInline):
|
|||
|
||||
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||
list_display = (
|
||||
'ident', 'status', 'type_link', 'account_link', 'created_on_display'
|
||||
'number', 'status', 'type_link', 'account_link', 'created_on_display'
|
||||
)
|
||||
list_filter = (BillTypeListFilter, 'status',)
|
||||
add_fields = ('account', 'type', 'status', 'due_on', 'comments')
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('number', 'account_link', 'type', 'status', 'due_on',
|
||||
'comments'),
|
||||
}),
|
||||
(_("Raw"), {
|
||||
'classes': ('collapse',),
|
||||
'fields': ('html',),
|
||||
}),
|
||||
)
|
||||
change_view_actions = [generate_bill]
|
||||
change_readonly_fields = ('account', 'type', 'status')
|
||||
readonly_fields = ('ident',)
|
||||
change_readonly_fields = ('account_link', 'type', 'status')
|
||||
readonly_fields = ('number',)
|
||||
inlines = [BillLineInline]
|
||||
|
||||
account_link = admin_link('account')
|
||||
created_on_display = admin_date('created_on')
|
||||
|
||||
def type_link(self, bill):
|
||||
|
@ -47,11 +73,27 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
type_link.short_description = _("type")
|
||||
type_link.admin_order_field = 'type'
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
fields = super(BillAdmin, self).get_readonly_fields(request, obj=obj)
|
||||
if obj and obj.status != Bill.OPEN:
|
||||
fields += self.add_fields
|
||||
return fields
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
if self.model is Budget:
|
||||
self.inlines = [BudgetLineInline]
|
||||
# Make parent object available for inline.has_add_permission()
|
||||
request.__bill__ = obj
|
||||
return super(BillAdmin, self).get_inline_instances(request, obj=obj)
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
""" Make value input widget bigger """
|
||||
if db_field.name == 'comments':
|
||||
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 4})
|
||||
if db_field.name == 'html':
|
||||
kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20})
|
||||
return super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
|
||||
|
||||
admin.site.register(Bill, BillAdmin)
|
||||
admin.site.register(Invoice, BillAdmin)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import inspect
|
||||
|
||||
from django.db import models
|
||||
from django.template import loader, Context
|
||||
from django.utils import timezone
|
||||
|
@ -43,7 +45,7 @@ class Bill(models.Model):
|
|||
('BUDGET', _("Budget")),
|
||||
)
|
||||
|
||||
ident = models.CharField(_("identifier"), max_length=16, unique=True,
|
||||
number = models.CharField(_("number"), max_length=16, unique=True,
|
||||
blank=True)
|
||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||
related_name='%(class)s')
|
||||
|
@ -62,7 +64,7 @@ class Bill(models.Model):
|
|||
objects = BillManager()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.ident
|
||||
return self.number
|
||||
|
||||
@cached_property
|
||||
def seller(self):
|
||||
|
@ -77,31 +79,34 @@ class Bill(models.Model):
|
|||
return self.billlines
|
||||
|
||||
@classmethod
|
||||
def get_type(cls):
|
||||
def get_class_type(cls):
|
||||
return cls.__name__.upper()
|
||||
|
||||
def set_ident(self):
|
||||
def get_type(self):
|
||||
return self.type or self.get_class_type()
|
||||
|
||||
def set_number(self):
|
||||
cls = type(self)
|
||||
bill_type = self.type or cls.get_type()
|
||||
bill_type = self.get_type()
|
||||
if bill_type == 'BILL':
|
||||
raise TypeError("get_new_ident() can not be used on a Bill class")
|
||||
raise TypeError("get_new_number() can not be used on a Bill class")
|
||||
# Bill number resets every natural year
|
||||
year = timezone.now().strftime("%Y")
|
||||
bills = cls.objects.filter(created_on__year=year)
|
||||
number_length = settings.BILLS_IDENT_NUMBER_LENGTH
|
||||
prefix = getattr(settings, 'BILLS_%s_IDENT_PREFIX' % bill_type)
|
||||
number_length = settings.BILLS_NUMBER_LENGTH
|
||||
prefix = getattr(settings, 'BILLS_%s_NUMBER_PREFIX' % bill_type)
|
||||
if self.status == self.OPEN:
|
||||
prefix = 'O{}'.format(prefix)
|
||||
bills = bills.filter(status=self.OPEN)
|
||||
num_bills = bills.order_by('-ident').first() or 0
|
||||
num_bills = bills.order_by('-number').first() or 0
|
||||
if num_bills is not 0:
|
||||
num_bills = int(num_bills.ident[-number_length:])
|
||||
num_bills = int(num_bills.number[-number_length:])
|
||||
else:
|
||||
bills = bills.exclude(status=self.OPEN)
|
||||
num_bills = bills.count()
|
||||
zeros = (number_length - len(str(num_bills))) * '0'
|
||||
number = zeros + str(num_bills + 1)
|
||||
self.ident = '{prefix}{year}{number}'.format(
|
||||
self.number = '{prefix}{year}{number}'.format(
|
||||
prefix=prefix, year=year, number=number)
|
||||
|
||||
def close(self):
|
||||
|
@ -130,9 +135,9 @@ class Bill(models.Model):
|
|||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.type:
|
||||
self.type = type(self).get_type()
|
||||
if not self.ident or (self.ident.startswith('O') and self.status != self.OPEN):
|
||||
self.set_ident()
|
||||
self.type = self.get_type()
|
||||
if not self.number or (self.number.startswith('O') and self.status != self.OPEN):
|
||||
self.set_number()
|
||||
super(Bill, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
|
@ -178,6 +183,14 @@ class BaseBillLine(models.Model):
|
|||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __unicode__(self):
|
||||
return "#%i" % self.number
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
lines = type(self).objects.filter(bill=self.bill_id)
|
||||
return lines.filter(id__lte=self.id).order_by('id').count()
|
||||
|
||||
|
||||
class BudgetLine(BaseBillLine):
|
||||
pass
|
||||
|
|
|
@ -17,6 +17,6 @@ class BillSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeriali
|
|||
class Meta:
|
||||
model = Bill
|
||||
fields = (
|
||||
'url', 'ident', 'bill_type', 'status', 'created_on', 'due_on',
|
||||
'url', 'number', 'bill_type', 'status', 'created_on', 'due_on',
|
||||
'comments', 'html', 'lines'
|
||||
)
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
BILLS_IDENT_NUMBER_LENGTH = getattr(settings, 'BILLS_IDENT_NUMBER_LENGTH', 4)
|
||||
BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4)
|
||||
|
||||
BILLS_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_INVOICE_IDENT_PREFIX', 'I')
|
||||
BILLS_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_INVOICE_NUMBER_PREFIX', 'I')
|
||||
|
||||
BILLS_AMENDMENT_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_IDENT_PREFIX', 'A')
|
||||
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX', 'A')
|
||||
|
||||
BILLS_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_FEE_IDENT_PREFIX', 'F')
|
||||
BILLS_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_FEE_NUMBER_PREFIX', 'F')
|
||||
|
||||
BILLS_AMENDMENT_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_IDENT_PREFIX', 'B')
|
||||
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_NUMBER_PREFIX', 'B')
|
||||
|
||||
BILLS_BUDGET_IDENT_PREFIX = getattr(settings, 'BILLS_BUDGET_IDENT_PREFIX', 'Q')
|
||||
BILLS_BUDGET_NUMBER_PREFIX = getattr(settings, 'BILLS_BUDGET_NUMBER_PREFIX', 'Q')
|
||||
|
||||
|
||||
BILLS_INVOICE_TEMPLATE = getattr(settings, 'BILLS_INVOICE_TEMPLATE', 'bills/microspective.html')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>{% block title %}Invoice - I20110223{% endblock %}</title>
|
||||
<title>{% block title %}{{ bill.get_type_display }} - {{ bill.number }}{% endblock %}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
|
|
|
@ -21,48 +21,47 @@
|
|||
</div>
|
||||
<div id="seller-details">
|
||||
<div claas="address">
|
||||
<span class="name">Associacio Pangea -<br>
|
||||
Coordinadora Comunicacio per a la Cooperacio</span>
|
||||
<span class="name">{{ seller.name }}</span>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<p>Pl. Eusebi Guell 6-7, planta 0<br>
|
||||
08034 - Barcelona<br>
|
||||
Spain<br>
|
||||
<p>{{ seller.address }}<br>
|
||||
{{ seller.zipcode }} - {{ seller.city }}<br>
|
||||
{{ seller.country }}<br>
|
||||
</p>
|
||||
<p><a href="tel:93-803-21-32">93-803-21-32</a><br>
|
||||
<a href="mailto:sales@pangea.org">sales@pangea.org</a><br>
|
||||
<a href="http://www.pangea.org">www.pangea.org</a></p>
|
||||
<p><a href="tel:93-803-21-32">{{ seller_info.phone }}</a><br>
|
||||
<a href="mailto:sales@pangea.org">{{ seller_info.email }}</a><br>
|
||||
<a href="http://www.pangea.org">{{ seller_info.website }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block summary %}
|
||||
<div id="bill-number">
|
||||
Invoice<br>
|
||||
<span class="value">F20110232</span><br>
|
||||
{{ bill.get_type_display }}<br>
|
||||
<span class="value">{{ bill.number }}</span><br>
|
||||
<span id="pagination">Page 1 of 1</span>
|
||||
</div>
|
||||
<div id="bill-summary">
|
||||
<hr>
|
||||
<div id="due-date">
|
||||
<span class="title">DUE DATE</span><br>
|
||||
<psan class="value">Nov 21, 2011</span>
|
||||
<psan class="value">{{ bill.due_on|date }}</span>
|
||||
</div>
|
||||
<div id="total">
|
||||
<span class="title">TOTAL</span><br>
|
||||
<psan class="value">122,03 €</span>
|
||||
<psan class="value">{{ bill.total }} &{{ currency.lower }};</span>
|
||||
</div>
|
||||
<div id="bill-date">
|
||||
<span class="title">INVOICE DATE</span><br>
|
||||
<psan class="value">Oct 20, 2012</span>
|
||||
<span class="title">{{ bill.get_type_display.upper }} DATE</span><br>
|
||||
<psan class="value">{{ bill.created_on|date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="buyer-details">
|
||||
<span class="name">Aadults</span><br>
|
||||
ES01939933<br>
|
||||
Carrer nnoseque, 0<br>
|
||||
08034 - Barcelona<br>
|
||||
Spain<br>
|
||||
<span class="name">{{ buyer.name }}</span><br>
|
||||
{{ buyer.vat }}<br>
|
||||
{{ buyer.address }}<br>
|
||||
{{ buyer.zipcode }} - {{ buyer.city }}<br>
|
||||
{{ buyer.country }}<br>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -74,50 +73,24 @@
|
|||
<span class="title column-rate">rate/price</span>
|
||||
<span class="title column-subtotal">subtotal</span>
|
||||
<br>
|
||||
<span class="value column-id">
|
||||
1<br>
|
||||
</span>
|
||||
<span class="value column-description">
|
||||
Hola que passa<br>
|
||||
nosquevols</span>
|
||||
<span class="value column-quantity">
|
||||
1<br>
|
||||
</span>
|
||||
<span class="value column-rate">
|
||||
1,00 €<br>
|
||||
</span>
|
||||
<span class="value column-subtotal">
|
||||
111,00 €<br>
|
||||
-10,00 €</span>
|
||||
<br>
|
||||
<span class="value column-id">1</span>
|
||||
<span class="value column-description">Merda pura</span>
|
||||
<span class="value column-quantity">1</span>
|
||||
<span class="value column-rate">1,00 €</span>
|
||||
<span class="value column-subtotal">111,00 €</span>
|
||||
<br>
|
||||
<span class="value column-id">1</span>
|
||||
<span class="value column-description">I tu que et passa</span>
|
||||
<span class="value column-quantity">1</span>
|
||||
<span class="value column-rate">1,00 €</span>
|
||||
<span class="value column-subtotal">111,00 €</span>
|
||||
<br>
|
||||
<span class="value column-id">1</span>
|
||||
<span class="value column-description">Joder hostia puta</span>
|
||||
<span class="value column-quantity">1</span>
|
||||
<span class="value column-rate">1,00 €</span>
|
||||
<span class="value column-subtotal">111,00 €</span>
|
||||
{% for line in bill.lines.all %}
|
||||
<span class="value column-id">{{ line.id }}</span>
|
||||
<span class="value column-description">{{ line.description }}</span>
|
||||
<span class="value column-quantity">{{ line.amount }}</span>
|
||||
<span class="value column-rate">{{ line.rate }}</span>
|
||||
<span class="value column-subtotal">{{ line.price }} &{{ currency.lower }};</span>
|
||||
<br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="totals">
|
||||
<span class="subtotal column-title">subtotal</span>
|
||||
<span class="subtotal column-value">33,03 €</span>
|
||||
<span class="subtotal column-value">{{ bill.subtotal }} &{{ currency.lower }};</span>
|
||||
<br>
|
||||
<span class="tax column-title">tax</span>
|
||||
<span class="tax column-value">33,03 €</span>
|
||||
<span class="tax column-value">{{ bill.taxes }} &{{ currency.lower }};</span>
|
||||
<br>
|
||||
<span class="total column-title">total</span>
|
||||
<span class="total column-value">33,03 €</span>
|
||||
<span class="total column-value">{{ bill.total }} &{{ currency.lower }};</span>
|
||||
<br>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -126,13 +99,14 @@
|
|||
<div id="footer">
|
||||
<div id="footer-column-1">
|
||||
<div id="comments">
|
||||
<span class="title">COMMENTS</span> The comments should be here. The comments should be here. The comments should be here. The comments should be here.
|
||||
{% if bill.comments %}
|
||||
<span class="title">COMMENTS</span> {{ bill.comments|linebreaksbr }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer-column-2">
|
||||
<div id="payment">
|
||||
<span class="title">PAYMENT</span> You can pay our invoice by bank transfer
|
||||
llkdskdlsdk The comments should be here. The comments should be here.
|
||||
<span class="title">PAYMENT</span> {{ bill.payment.message }}
|
||||
</div>
|
||||
<div id="questions">
|
||||
<span class="title">QUESTIONS</span> If you have any question about your bill, please
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
import re
|
||||
|
||||
from django.contrib.auth.hashers import check_password, make_password
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.core import services
|
||||
|
||||
from . import validators, settings
|
||||
|
||||
|
||||
class Mailbox(models.Model):
|
||||
name = models.CharField(_("name"), max_length=64, unique=True,
|
||||
help_text=_("Required. 30 characters or fewer. Letters, digits and "
|
||||
"@/./+/-/_ only."),
|
||||
validators=[RegexValidator(r'^[\w.@+-]+$',
|
||||
_("Enter a valid username."), 'invalid')])
|
||||
use_custom_filtering = models.BooleanField(_("Use custom filtering"),
|
||||
default=False)
|
||||
custom_filtering = models.TextField(_("filtering"), blank=True,
|
||||
validators=[validators.validate_sieve],
|
||||
help_text=_("Arbitrary email filtering in sieve language."))
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = _("mailboxes")
|
||||
|
||||
def __unicode__(self):
|
||||
return self.user.username
|
||||
|
||||
# def get_addresses(self):
|
||||
# regex = r'(^|\s)+%s(\s|$)+' % self.user.username
|
||||
# return Address.objects.filter(destination__regex=regex)
|
||||
#
|
||||
# def delete(self, *args, **kwargs):
|
||||
# """ Update related addresses """
|
||||
# regex = re.compile(r'(^|\s)+(\s*%s)(\s|$)+' % self.user.username)
|
||||
# super(Mailbox, self).delete(*args, **kwargs)
|
||||
# for address in self.get_addresses():
|
||||
# address.destination = regex.sub(r'\3', address.destination).strip()
|
||||
# if not address.destination:
|
||||
# address.delete()
|
||||
# else:
|
||||
# address.save()
|
||||
|
||||
|
||||
#class Address(models.Model):
|
||||
# name = models.CharField(_("name"), max_length=64,
|
||||
# validators=[validators.validate_emailname])
|
||||
# domain = models.ForeignKey(settings.EMAILS_DOMAIN_MODEL,
|
||||
# verbose_name=_("domain"),
|
||||
# related_name='addresses')
|
||||
# destination = models.CharField(_("destination"), max_length=256,
|
||||
# validators=[validators.validate_destination],
|
||||
# help_text=_("Space separated mailbox names or email addresses"))
|
||||
# account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||
# related_name='addresses')
|
||||
#
|
||||
# class Meta:
|
||||
# verbose_name_plural = _("addresses")
|
||||
# unique_together = ('name', 'domain')
|
||||
#
|
||||
# def __unicode__(self):
|
||||
# return self.email
|
||||
#
|
||||
# @property
|
||||
# def email(self):
|
||||
# return "%s@%s" % (self.name, self.domain)
|
||||
#
|
||||
# def get_mailboxes(self):
|
||||
# for dest in self.destination.split():
|
||||
# if '@' not in dest:
|
||||
# yield Mailbox.objects.select_related('user').get(user__username=dest)
|
||||
|
||||
|
||||
class Address(models.Model):
|
||||
name = models.CharField(_("name"), max_length=64,
|
||||
validators=[validators.validate_emailname])
|
||||
domain = models.ForeignKey(settings.EMAILS_DOMAIN_MODEL,
|
||||
verbose_name=_("domain"),
|
||||
related_name='addresses')
|
||||
mailboxes = models.ManyToManyField('mail.Mailbox', verbose_name=_("mailboxes"),
|
||||
related_name='addresses', blank=True)
|
||||
forward = models.CharField(_("forward"), max_length=256, blank=True,
|
||||
validators=[validators.validate_forward])
|
||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||
related_name='addresses')
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = _("addresses")
|
||||
unique_together = ('name', 'domain')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.email
|
||||
|
||||
@property
|
||||
def email(self):
|
||||
return "%s@%s" % (self.name, self.domain)
|
||||
|
||||
|
||||
class Autoresponse(models.Model):
|
||||
address = models.OneToOneField(Address, verbose_name=_("address"),
|
||||
related_name='autoresponse')
|
||||
# TODO initial_date
|
||||
subject = models.CharField(_("subject"), max_length=256)
|
||||
message = models.TextField(_("message"))
|
||||
enabled = models.BooleanField(_("enabled"), default=False)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.address
|
||||
|
||||
|
||||
services.register(Address)
|
Loading…
Reference in New Issue