Added resource history and domain record massive edditing

This commit is contained in:
Marc Aymerich 2015-07-17 13:29:29 +00:00
parent 63c9f381bc
commit 0d9058d266
18 changed files with 765 additions and 67 deletions

View file

@ -423,3 +423,10 @@ Colaesce('total', 'computed_total')
Case
# case on payment transaction state ? case when trans.amount >
# bill changelist: dates: (closed_on, created_on, updated_on)
# Add record modeladmin action: select domains + add records (formset) to selected domains
# Resource data inline show info: link to monitor data, and history chart: link to monitor data of each item

View file

@ -47,7 +47,8 @@ class AdminFormSet(BaseModelFormSet):
def adminmodelformset_factory(modeladmin, form, formset=AdminFormSet, **kwargs):
formset = modelformset_factory(modeladmin.model, form=form, formset=formset, **kwargs)
model = kwargs.pop('model', modeladmin.model)
formset = modelformset_factory(model, form=form, formset=formset, **kwargs)
formset.modeladmin = modeladmin
return formset

View file

@ -200,10 +200,6 @@ class Bill(models.Model):
errors['amend_of'] = _("Related invoice is an amendment.")
if errors:
raise ValidationError(errors)
elif self.type in self.AMEND_MAP.values():
raise ValidationError({
'amend_of': _("Type %s requires an amend of link.") % self.get_type_display()
})
def get_payment_state_display(self):
value = self.payment_state

View file

@ -1,5 +1,17 @@
import copy
from django.contrib.admin import helpers
from django.shortcuts import render
from django.utils.safestring import mark_safe
from django.utils.translation import ungettext, ugettext_lazy as _
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.forms import adminmodelformset_factory
from orchestra.admin.utils import get_object_from_url, change_url
from orchestra.utils.python import AttrDict
from .forms import RecordForm, RecordEditFormSet
from .models import Record
def view_zone(modeladmin, request, queryset):
@ -12,3 +24,68 @@ def view_zone(modeladmin, request, queryset):
return TemplateResponse(request, 'admin/domains/domain/view_zone.html', context)
view_zone.url_name = 'view-zone'
view_zone.verbose_name = _("View zone")
def edit_records(modeladmin, request, queryset):
formsets = []
for domain in queryset.prefetch_related('records'):
modeladmin_copy = copy.copy(modeladmin)
modeladmin_copy.model = Record
link = '<a href="%s">%s</a>' % (change_url(domain), domain.name)
modeladmin_copy.verbose_name_plural = mark_safe(link)
RecordFormSet = adminmodelformset_factory(
modeladmin_copy, RecordForm, formset=RecordEditFormSet, extra=1, can_delete=True)
formset = RecordFormSet(queryset=domain.records.all(), prefix=domain.id)
formset.instance = domain
formset.cls = RecordFormSet
formsets.append(formset)
if request.POST.get('post') == 'generic_confirmation':
posted_formsets = []
all_valid = True
for formset in formsets:
instance = formset.instance
formset = formset.cls(
request.POST, request.FILES, queryset=formset.queryset, prefix=instance.id)
formset.instance = instance
if not formset.is_valid():
all_valid = False
posted_formsets.append(formset)
formsets = posted_formsets
if all_valid:
for formset in formsets:
for form in formset.forms:
form.instance.domain_id = formset.instance.id
formset.save()
fake_form = AttrDict({
'changed_data': False
})
change_message = modeladmin.construct_change_message(request, fake_form, [formset])
modeladmin.log_change(request, formset.instance, change_message)
num = len(formsets)
message = ungettext(
_("Records for one selected domain have been updated."),
_("Records for %i selected domains have been updated.") % num,
num)
modeladmin.message_user(request, message)
return
opts = modeladmin.model._meta
context = {
'title': _("Edit records"),
'action_name': 'Edit records',
'action_value': 'edit_records',
'display_objects': [],
'queryset': queryset,
'opts': opts,
'app_label': opts.app_label,
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
'formsets': formsets,
'obj': get_object_from_url(modeladmin, request),
}
return render(request, 'admin/domains/domain/edit_records.html', context)
def add_records(modeladmin, request, queryset):
# TODO
pass

View file

@ -8,24 +8,17 @@ from orchestra.admin.utils import admin_link, change_url
from orchestra.contrib.accounts.admin import AccountAdminMixin
from orchestra.utils import apps
from .actions import view_zone
from .actions import view_zone, edit_records
from .filters import TopDomainListFilter
from .forms import RecordInlineFormSet, BatchDomainCreationAdminForm
from .forms import RecordForm, RecordInlineFormSet, BatchDomainCreationAdminForm
from .models import Domain, Record
class RecordInline(admin.TabularInline):
model = Record
form = RecordForm
formset = RecordInlineFormSet
verbose_name_plural = _("Extra records")
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
if db_field.name == 'value':
kwargs['widget'] = forms.TextInput(attrs={'size':'100'})
if db_field.name == 'ttl':
kwargs['widget'] = forms.TextInput(attrs={'size':'10'})
return super(RecordInline, self).formfield_for_dbfield(db_field, **kwargs)
class DomainInline(admin.TabularInline):
@ -63,6 +56,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
change_readonly_fields = ('name', 'serial')
search_fields = ('name', 'account__username')
add_form = BatchDomainCreationAdminForm
actions = (edit_records,)
change_view_actions = [view_zone]
def structured_name(self, domain):

View file

@ -2,6 +2,8 @@ from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.forms import AdminFormSet
from . import validators
from .helpers import domain_for_validation
from .models import Domain
@ -63,17 +65,35 @@ class BatchDomainCreationAdminForm(forms.ModelForm):
return cleaned_data
class RecordInlineFormSet(forms.models.BaseInlineFormSet):
class RecordForm(forms.ModelForm):
class Meta:
fields = ('ttl', 'type', 'value')
def __init__(self, *args, **kwargs):
super(RecordForm, self).__init__(*args, **kwargs)
self.fields['ttl'].widget = forms.TextInput(attrs={'size':'10'})
self.fields['value'].widget = forms.TextInput(attrs={'size':'100'})
class ValidateZoneMixin(object):
def clean(self):
""" Checks if everything is consistent """
super(RecordInlineFormSet, self).clean()
if any(self.errors):
super(ValidateZoneMixin, self).clean()
if any(formset.errors):
return
if self.instance.name:
if formset.instance.name:
records = []
for form in self.forms:
for form in formset.forms:
data = form.cleaned_data
if data and not data['DELETE']:
records.append(data)
domain = domain_for_validation(self.instance, records)
domain = domain_for_validation(formset.instance, records)
validators.validate_zone(domain.render_zone())
class RecordEditFormSet(ValidateZoneMixin, AdminFormSet):
pass
class RecordInlineFormSet(ValidateZoneMixin, forms.models.BaseInlineFormSet):
pass

View file

@ -20,7 +20,7 @@ class Domain(models.Model):
top = models.ForeignKey('domains.Domain', null=True, related_name='subdomain_set',
editable=False)
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial, editable=False,
help_text=_("A revision number that changes whenever you update your domain."))
help_text=_("A revision number that changes whenever this domain is updated."))
refresh = models.IntegerField(_("refresh"), null=True, blank=True,
validators=[validators.validate_zone_interval],
help_text=_("The time a secondary DNS server waits before querying the primary DNS "
@ -182,10 +182,10 @@ class Domain(models.Model):
"%s." % settings.DOMAINS_DEFAULT_NAME_SERVER,
utils.format_hostmaster(settings.DOMAINS_DEFAULT_HOSTMASTER),
str(self.serial),
settings.DOMAINS_DEFAULT_REFRESH if self.refresh is None else self.refresh,
settings.DOMAINS_DEFAULT_RETRY if self.retry is None else self.retry,
settings.DOMAINS_DEFAULT_EXPIRE if self.expire is None else self.expire,
settings.DOMAINS_DEFAULT_MIN_TTL if self.min_ttl is None else self.min_ttl,
self.refresh or settings.DOMAINS_DEFAULT_REFRESH,
self.retry or settings.DOMAINS_DEFAULT_RETRY,
self.expire or settings.DOMAINS_DEFAULT_EXPIRE,
self.min_ttl or settings.DOMAINS_DEFAULT_MIN_TTL,
]
records.insert(0, AttrDict(
type=Record.SOA,
@ -272,13 +272,24 @@ class Record(models.Model):
(SOA, "SOA"),
)
VALIDATORS = {
MX: validators.validate_mx_record,
NS: validators.validate_zone_label,
A: validate_ipv4_address,
AAAA: validate_ipv6_address,
CNAME: validators.validate_zone_label,
TXT: validate_ascii,
SRV: validators.validate_srv_record,
SOA: validators.validate_soa_record,
}
domain = models.ForeignKey(Domain, verbose_name=_("domain"), related_name='records')
ttl = models.CharField(_("TTL"), max_length=8, blank=True,
help_text=_("Record TTL, defaults to %s") % settings.DOMAINS_DEFAULT_TTL,
validators=[validators.validate_zone_interval])
type = models.CharField(_("type"), max_length=32, choices=TYPE_CHOICES)
value = models.CharField(_("value"), max_length=256, help_text=_("MX, NS and CNAME records "
"sould end with a dot."))
value = models.CharField(_("value"), max_length=256,
help_text=_("MX, NS and CNAME records sould end with a dot."))
def __str__(self):
return "%s %s IN %s %s" % (self.domain, self.get_ttl(), self.type, self.value)
@ -288,20 +299,13 @@ class Record(models.Model):
# validate value
if self.type != self.TXT:
self.value = self.value.lower().strip()
choices = {
self.MX: validators.validate_mx_record,
self.NS: validators.validate_zone_label,
self.A: validate_ipv4_address,
self.AAAA: validate_ipv6_address,
self.CNAME: validators.validate_zone_label,
self.TXT: validate_ascii,
self.SRV: validators.validate_srv_record,
self.SOA: validators.validate_soa_record,
}
try:
choices[self.type](self.value)
except ValidationError as error:
raise ValidationError({'value': error})
if self.type:
try:
self.VALIDATORS[self.type](self.value)
except ValidationError as error:
raise ValidationError({
'value': error,
})
def get_ttl(self):
return self.ttl or settings.DOMAINS_DEFAULT_TTL

View file

@ -0,0 +1,20 @@
{% extends "admin/orchestra/generic_confirmation.html" %}
{% load static %}
{% block extrahead %}
{{ block.super }}
<script type="text/javascript" src="{% static 'admin/js/core.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/admin/RelatedObjectLookups.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/jquery.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/jquery.init.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/actions.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/collapse.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/inlines.js' %}"></script>
{% endblock %}
{% block formset %}
{% for formset in formsets %}
{{ formset.as_admin }}
{% endfor %}
{% endblock %}

View file

@ -1,5 +1,6 @@
from threading import local
from django.contrib.admin.models import LogEntry
from django.core.urlresolvers import resolve
from django.db import transaction
from django.db.models.signals import pre_delete, post_save, m2m_changed
@ -15,14 +16,14 @@ from .models import BackendLog
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')
def post_save_collector(sender, *args, **kwargs):
if sender not in [BackendLog, Operation]:
if sender not in (BackendLog, Operation, LogEntry):
instance = kwargs.get('instance')
OperationsMiddleware.collect(Operation.SAVE, **kwargs)
@receiver(pre_delete, dispatch_uid='orchestration.pre_delete_collector')
def pre_delete_collector(sender, *args, **kwargs):
if sender not in [BackendLog, Operation]:
if sender not in (BackendLog, Operation, LogEntry):
OperationsMiddleware.collect(Operation.DELETE, **kwargs)

View file

@ -62,5 +62,5 @@ def history(modeladmin, request, queryset):
context = {
'resources': resources,
}
return render(request, 'admin/resources/resourcedata/report.html', context)
return render(request, 'admin/resources/resourcedata/history.html', context)
history.url_name = 'history'

View file

@ -254,16 +254,25 @@ def resource_inline_factory(resources):
display_updated = admin_date('updated_at', default=_("Never"))
def display_used(self, data):
from django.templatetags.static import static
update_link = ''
history_link = ''
if data.pk:
update_url = reverse('admin:resources_resourcedata_monitor', args=(data.pk,))
update_link = '<a href="%s"><strong>%s</strong></a>' % (update_url, _("Update"))
history_url = reverse('admin:resources_resourcedata_history', args=(data.pk,))
popup = 'onclick="return showAddAnotherPopup(this);"'
history_link = '<a href="%s" %s>%s</a>' % (history_url, popup, _("History"))
context = {
'title': _("Update"),
'url': reverse('admin:resources_resourcedata_monitor', args=(data.pk,)),
'image': '<img src="%s"></img>' % static('orchestra/images/reload.png'),
}
update = '<a href="%(url)s" title="%(title)s">%(image)s</a>' % context
context.update({
'title': _("Show history"),
'image': '<img src="%s"></img>' % static('orchestra/images/history.png'),
'url': reverse('admin:resources_resourcedata_history', args=(data.pk,)),
'popup': 'onclick="return showAddAnotherPopup(this);"',
})
history = '<a href="%(url)s" title="%(title)s" %(popup)s>%(image)s</a>' % context
if data.used is not None:
return ' '.join(map(str, (data.used, data.resource.unit, update_link, history_link)))
return ' '.join(map(str, (data.used, data.resource.unit, update, history)))
return _("Unknonw %s") % update_link
display_used.short_description = _("Used")
display_used.allow_tags = True

View file

@ -2,7 +2,7 @@
<html>
<head>
<title>Transaction Report</title>
<title>Resource history</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script src="/static/orchestra/js/Chart.min.js"></script>
<style type="text/css">
@ -64,7 +64,7 @@
}
new Chart(income).Bar(barData, options);
</script>
<table id="summary">
<tr class="header">
<th class="title column-name">{% trans "Date" %}</th>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,358 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="90.000000"
inkscape:export-xdpi="90.000000"
inkscape:export-filename="/home/glic3/orchestra/django-orchestra/orchestra/static/orchestra/images/history.png"
width="16"
height="16"
id="svg11300"
sodipodi:version="0.32"
inkscape:version="0.91 r"
sodipodi:docname="history.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.1">
<defs
id="defs3">
<linearGradient
inkscape:collect="always"
id="linearGradient5204">
<stop
style="stop-color:#c4a000;stop-opacity:1;"
offset="0"
id="stop5206" />
<stop
style="stop-color:#c4a000;stop-opacity:0;"
offset="1"
id="stop5208" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5196">
<stop
style="stop-color:#c4a000;stop-opacity:1;"
offset="0"
id="stop5198" />
<stop
style="stop-color:#c4a000;stop-opacity:0;"
offset="1"
id="stop5200" />
</linearGradient>
<linearGradient
id="linearGradient12512">
<stop
style="stop-color:#ffffff;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop12513" />
<stop
style="stop-color:#fff520;stop-opacity:0.89108908;"
offset="0.50000000"
id="stop12517" />
<stop
style="stop-color:#fff300;stop-opacity:0.0000000;"
offset="1.0000000"
id="stop12514" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient12512"
id="radialGradient278"
gradientUnits="userSpaceOnUse"
cx="55"
cy="125"
fx="55"
fy="125"
r="14.375" />
<linearGradient
id="linearGradient10653">
<stop
style="stop-color:#f3f4ff;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop10655" />
<stop
style="stop-color:#9193af;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop10657" />
</linearGradient>
<linearGradient
id="linearGradient42174">
<stop
style="stop-color:#a0a0a0;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop42176" />
<stop
style="stop-color:#ffffff;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop42178" />
</linearGradient>
<linearGradient
id="linearGradient2145">
<stop
style="stop-color:#fffffd;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop2147" />
<stop
style="stop-color:#cbcbc9;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop2149" />
</linearGradient>
<linearGradient
id="linearGradient37935">
<stop
id="stop37937"
offset="0.0000000"
style="stop-color:#9497b3;stop-opacity:1.0000000;" />
<stop
id="stop37939"
offset="1.0000000"
style="stop-color:#4c4059;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient2152">
<stop
id="stop2154"
offset="0.0000000"
style="stop-color:#9aa29a;stop-opacity:1.0000000;" />
<stop
id="stop2156"
offset="1.0000000"
style="stop-color:#b5beb5;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2152"
id="linearGradient4307"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1323593,0,0,0.35150265,-12.192171,24.700536)"
x1="8.9156475"
y1="37.197018"
x2="9.8855038"
y2="52.090679" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10653"
id="radialGradient4309"
gradientUnits="userSpaceOnUse"
cx="11.3292"
cy="10.58397"
fx="11.3292"
fy="10.58397"
r="15.532059"
gradientTransform="matrix(0.49213503,0,0,0.49213503,0.00830489,31.624507)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2145"
id="radialGradient4311"
gradientUnits="userSpaceOnUse"
cx="11.901996"
cy="10.045444"
fx="11.901996"
fy="10.045444"
r="29.292715"
gradientTransform="matrix(0.42187886,0,0,0.42187886,1.1156771,32.810317)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient42174"
id="linearGradient4313"
gradientUnits="userSpaceOnUse"
x1="6.342216"
y1="7.7893324"
x2="22.218424"
y2="25.884274"
gradientTransform="matrix(0.42187886,0,0,0.42187886,1.1156771,32.810317)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5196"
id="radialGradient5202"
cx="23.375"
cy="10.972863"
fx="23.375"
fy="10.972863"
r="3.3478093"
gradientTransform="matrix(2.3292353,0,0,2.4008659,-46.253114,13.661846)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5204"
id="linearGradient5210"
x1="19.667364"
y1="4.2570662"
x2="20.329933"
y2="5.2845874"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.64158826,0,0,0.64158826,-6.8043677,32.387358)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient37935"
id="radialGradient5212"
gradientUnits="userSpaceOnUse"
cx="8.7468252"
cy="6.8283234"
fx="8.7468252"
fy="6.8283234"
r="29.889715"
gradientTransform="matrix(0.51891397,0,0,0.51891397,-0.4268392,31.2037)" />
</defs>
<sodipodi:namedview
stroke="#c4a000"
fill="#babdb6"
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0.25490196"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="2.7636966"
inkscape:cy="11.830339"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:showpageshadow="false"
inkscape:window-width="1920"
inkscape:window-height="1024"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<dc:source>http://jimmac.musichall.cz</dc:source>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
<dc:title>New Appointment</dc:title>
<dc:subject>
<rdf:Bag>
<rdf:li>appointment</rdf:li>
<rdf:li>new</rdf:li>
<rdf:li>meeting</rdf:li>
<rdf:li>rvsp</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Attribution" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0,-32)">
<path
sodipodi:nodetypes="cccc"
id="path14341"
d="m 6.1045434,32.312322 -5.20565338,6.051009 0.45627258,0.45064 4.7493808,-6.501649 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient4307);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="cccc"
id="path18921"
d="M 6.0608909,32.279726 1.4186351,38.717102 2.081818,39.302631 6.0608909,32.279726 Z"
style="fill:#fefefe;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
inkscape:connector-curvature="0" />
<circle
id="path27786"
style="fill:url(#radialGradient5212);fill-opacity:1;fill-rule:evenodd;stroke:#605773;stroke-width:0.36248955;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
cx="8.0055094"
cy="39.978912"
r="7.7373796" />
<circle
id="path35549"
style="fill:url(#radialGradient4311);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient4313);stroke-width:0.30012295;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
cx="7.9712114"
cy="39.944607"
r="6.2905145" />
<path
sodipodi:type="arc"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient5202);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient5210);stroke-width:0.36248916;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path4120"
sodipodi:cx="8.1927567"
sodipodi:cy="40.006229"
sodipodi:rx="5.4535012"
sodipodi:ry="5.4535012"
d="m 3.8969257,36.646689 a 5.4535012,5.4535012 0 0 1 4.2686858,-2.093893 l 0.027145,5.453433 z"
sodipodi:start="3.8052902"
sodipodi:end="4.7074114" />
<circle
id="path34778"
style="fill:#f3f3f3;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.36248925;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
cx="8.1309881"
cy="40.029209"
r="0.91594833" />
<path
id="path35559"
d="M 7.4055247,39.313544 4.135537,36.5667"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:0.36248925;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path35561"
d="M 6.4021514,42.59715 7.4751372,40.968385"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:0.72497851;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0" />
<circle
id="path35563"
style="opacity:1;fill:#b6b9b1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.36871839;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
cx="7.9325924"
cy="34.972736"
r="0.61665297" />
<circle
id="path35565"
style="opacity:1;fill:#b6b9b1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.36871839;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
cx="7.9325924"
cy="44.839195"
r="0.61665297" />
<circle
id="path35567"
style="opacity:1;fill:#b6b9b1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.36871839;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
cx="2.9993658"
cy="39.905964"
r="0.61665297" />
<circle
id="path35569"
style="opacity:1;fill:#b6b9b1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.36871839;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
cx="12.865818"
cy="39.905964"
r="0.61665297" />
<circle
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#radialGradient4309);stroke-width:0.36248961;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
id="path10651"
cx="8.0055075"
cy="39.946884"
r="7.3380861" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

View file

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="16"
height="16"
id="svg2473"
inkscape:version="0.91 r"
sodipodi:docname="reload.svg"
inkscape:export-filename="/home/glic3/orchestra/django-orchestra/orchestra/static/orchestra/images/reload.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<metadata
id="metadata37">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1024"
id="namedview35"
showgrid="false"
inkscape:zoom="14.75"
inkscape:cx="-3.3220339"
inkscape:cy="8"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg2473" />
<defs
id="defs2475">
<linearGradient
id="linearGradient3719">
<stop
id="stop3721"
style="stop-color:#538ec6;stop-opacity:1"
offset="0" />
<stop
id="stop3723"
style="stop-color:#538ec6;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3709">
<stop
id="stop3711"
style="stop-color:#6396cd;stop-opacity:0"
offset="0" />
<stop
id="stop3713"
style="stop-color:#6396cd;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3533">
<stop
id="stop3535"
style="stop-color:#93b9dd;stop-opacity:1"
offset="0" />
<stop
id="stop3545"
style="stop-color:#6396cd;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
x1="108.97799"
y1="230.02158"
x2="107.36588"
y2="224.30264"
id="linearGradient6283"
xlink:href="#linearGradient6277"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient6277">
<stop
id="stop6279"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop6281"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
x1="107.25907"
y1="222.87531"
x2="108.83574"
y2="226.83432"
id="linearGradient6291"
xlink:href="#linearGradient6285"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient6285">
<stop
id="stop6287"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop6289"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
x1="8"
y1="0"
x2="9"
y2="9"
id="linearGradient3697"
xlink:href="#linearGradient3533"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="107.98247"
y1="219.63542"
x2="108.84705"
y2="227.40942"
id="linearGradient3705"
xlink:href="#linearGradient3533"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="112.3054"
y1="227.40942"
x2="108.84705"
y2="230.86455"
id="linearGradient3715"
xlink:href="#linearGradient3709"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="110.58552"
y1="230.02382"
x2="114.06341"
y2="227.41991"
id="linearGradient3725"
xlink:href="#linearGradient3719"
gradientUnits="userSpaceOnUse" />
</defs>
<g
transform="translate(-103,-219)"
id="g6293"
style="stroke:#000000;stroke-opacity:1;display:inline;enable-background:new">
<path
d="m 112.89199,228.29933 a 4.75,4.75 0 0 1 -6.41224,0.8092"
transform="matrix(1.1501219,0,0,1.1521155,-15.186827,-33.014002)"
id="path4517-5"
style="color:#000000;fill:none;stroke:url(#linearGradient3725);stroke-width:3.47488189;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
<path
d="m 116,221 0,4 -4,0"
id="path4519-1-9"
style="fill:none;stroke:#538ec6;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="m 106.86806,229.35961 a 4.75,4.75 0 1 1 6.22978,-6.89468"
transform="matrix(1.1501219,0,0,1.1521155,-15.186827,-33.014002)"
id="path3717"
style="color:#000000;fill:none;stroke:#538ec6;stroke-width:3.47488189;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
<path
d="m 107.99194,229.83037 a 4.75,4.75 0 1 1 5.1059,-7.36544"
transform="matrix(1.1566222,0,0,1.1577034,-118.89492,-253.27267)"
id="path4517"
style="color:#000000;fill:none;stroke:url(#linearGradient3705);stroke-width:1.72836542;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
<path
d="m 12.999998,2 0,4 -4.0000006,0"
id="path4519-7"
style="fill:none;stroke:url(#linearGradient3697);stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline;enable-background:new" />
<path
d="m 11,5.5 -2.0000026,0 M 12.499998,2 l 0,1.3"
id="path4519-8-7"
style="opacity:0.23999999;fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline;enable-background:new" />
<path
d="m 112.88153,228.31178 a 4.75,4.75 0 0 1 -5.33461,1.37241"
transform="matrix(1.1566222,0,0,1.1577034,-118.89492,-253.27267)"
id="path3707"
style="color:#000000;fill:none;stroke:url(#linearGradient3715);stroke-width:1.72836542;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
<path
d="m 106.51558,229.134 a 4.75,4.75 0 1 1 6.58226,-6.66907"
transform="matrix(1.2684974,0,0,1.2629385,-131.05785,-276.9778)"
id="path4517-8"
style="opacity:0.23999999;color:#000000;fill:none;stroke:url(#linearGradient6291);stroke-width:0.79006732;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
<path
d="m 111.90936,229.18578 a 4.75,4.75 0 1 1 1.18848,-6.72085"
transform="matrix(1.0467613,0,0,1.0546091,-106.88644,-230.0516)"
id="path4517-8-9"
style="opacity:0.23999999;color:#000000;fill:none;stroke:url(#linearGradient6283);stroke-width:0.95176643;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View file

@ -32,6 +32,7 @@
<p>{{ content_message | safe }}</p>
<ul>{{ display_objects | unordered_list }}</ul>
<form action="" method="post">{% csrf_token %}
{% block form %}
{% if form %}
<fieldset class="module aligned">
{{ form.non_field_errors }}
@ -50,9 +51,12 @@
{% endfor %}
</fieldset>
{% endif %}
{% endblock %}
{% block formset %}
{% if formset %}
{{ formset.as_admin }}
{% endif %}
{% endblock %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />

View file

@ -40,7 +40,7 @@ def _un(singular__plural, n=None):
return ungettext(singular, plural, n)
def naturaldatetime(date, include_seconds=False):
def naturaldatetime(date, include_seconds=True):
"""Convert datetime into a human natural date string."""
if not date:
return ''
@ -56,35 +56,35 @@ def naturaldatetime(date, include_seconds=False):
minutes = delta.seconds / 60
seconds = delta.seconds
ago = ' ago'
ago = " ago"
if days < 0:
ago = ''
ago = ""
days = abs(days)
if days == 0:
if hours == 0:
if minutes > 0:
if minutes >= 1:
minutes = float(seconds)/60
return ungettext(
_('{minutes:.1f} minute{ago}'),
_('{minutes:.1f} minutes{ago}'), minutes
_("{minutes:.1f} minute{ago}"),
_("{minutes:.1f} minutes{ago}"), minutes
).format(minutes=minutes, ago=ago)
else:
if include_seconds and seconds:
if include_seconds:
return ungettext(
_('{seconds} second{ago}'),
_('{seconds} seconds{ago}'), seconds
_("{seconds} second{ago}"),
_("{seconds} seconds{ago}"), seconds
).format(seconds=seconds, ago=ago)
return _('just now')
return _("just now")
else:
hours = float(minutes)/60
return ungettext(
_('{hours:.1f} hour{ago}'),
_('{hours:.1f} hours{ago}'), hours
_("{hours:.1f} hour{ago}"),
_("{hours:.1f} hours{ago}"), hours
).format(hours=hours, ago=ago)
if delta_midnight.days == 0:
return _('yesterday at {time}').format(time=date.strftime('%H:%M'))
return _("yesterday at {time}").format(time=date.strftime('%H:%M'))
count = 0
for chunk, pluralizefun in OLDER_CHUNKS: