Random improvements
This commit is contained in:
parent
b4792f8cbf
commit
f21091d0c2
6
TODO.md
6
TODO.md
|
@ -35,3 +35,9 @@ TODO
|
||||||
* git deploy in addition to FTP?
|
* git deploy in addition to FTP?
|
||||||
* env vars instead of multiple settings files: https://devcenter.heroku.com/articles/config-vars ?
|
* env vars instead of multiple settings files: https://devcenter.heroku.com/articles/config-vars ?
|
||||||
* optional chroot shell?
|
* optional chroot shell?
|
||||||
|
|
||||||
|
* make sure prefetch_related() is used correctly
|
||||||
|
Remember that, as always with QuerySets, any subsequent chained methods which imply a different database query will ignore previously cached results, and retrieve data using a fresh database query.
|
||||||
|
* profile select_related vs prefetch_related
|
||||||
|
|
||||||
|
* use HTTP OPTIONS instead of configuration endpoint, or rename to settings?
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
from django import conf
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from rest_framework import views
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.reverse import reverse
|
|
||||||
from rest_framework.routers import DefaultRouter, Route, flatten, replace_methodname
|
from rest_framework.routers import DefaultRouter, Route, flatten, replace_methodname
|
||||||
|
|
||||||
from .. import settings
|
from orchestra import settings
|
||||||
from ..utils.apps import autodiscover as module_autodiscover
|
from orchestra.utils.apps import autodiscover as module_autodiscover
|
||||||
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
from .helpers import insert_links, replace_collectionmethodname
|
from .helpers import insert_links, replace_collectionmethodname
|
||||||
|
from .root import APIRoot
|
||||||
|
|
||||||
|
|
||||||
def collectionlink(**kwargs):
|
def collectionlink(**kwargs):
|
||||||
|
@ -79,39 +78,8 @@ class LinkHeaderRouter(DefaultRouter):
|
||||||
|
|
||||||
def get_api_root_view(self):
|
def get_api_root_view(self):
|
||||||
""" returns the root view, with all the linked collections """
|
""" returns the root view, with all the linked collections """
|
||||||
class APIRoot(views.APIView):
|
APIRoot = import_class(settings.API_ROOT_VIEW)
|
||||||
def get(instance, request, format=None):
|
APIRoot.router = self
|
||||||
root_url = reverse('api-root', request=request, format=format)
|
|
||||||
token_url = reverse('api-token-auth', request=request, format=format)
|
|
||||||
links = [
|
|
||||||
'<%s>; rel="%s"' % (root_url, 'api-root'),
|
|
||||||
'<%s>; rel="%s"' % (token_url, 'api-get-auth-token'),
|
|
||||||
]
|
|
||||||
if not request.user.is_anonymous():
|
|
||||||
list_name = '{basename}-list'
|
|
||||||
detail_name = '{basename}-detail'
|
|
||||||
for prefix, viewset, basename in self.registry:
|
|
||||||
singleton_pk = getattr(viewset, 'singleton_pk', False)
|
|
||||||
if singleton_pk:
|
|
||||||
url_name = detail_name.format(basename=basename)
|
|
||||||
kwargs = { 'pk': singleton_pk(viewset(), request) }
|
|
||||||
else:
|
|
||||||
url_name = list_name.format(basename=basename)
|
|
||||||
kwargs = {}
|
|
||||||
url = reverse(url_name, request=request, format=format, kwargs=kwargs)
|
|
||||||
links.append('<%s>; rel="%s"' % (url, url_name))
|
|
||||||
# Add user link
|
|
||||||
url_name = detail_name.format(basename='user')
|
|
||||||
kwargs = { 'pk': request.user.pk }
|
|
||||||
url = reverse(url_name, request=request, format=format, kwargs=kwargs)
|
|
||||||
links.append('<%s>; rel="%s"' % (url, url_name))
|
|
||||||
headers = { 'Link': ', '.join(links) }
|
|
||||||
content = {
|
|
||||||
name: getattr(settings, name, None)
|
|
||||||
for name in ['SITE_NAME', 'SITE_VERBOSE_NAME']
|
|
||||||
}
|
|
||||||
content['INSTALLED_APPS'] = conf.settings.INSTALLED_APPS
|
|
||||||
return Response(content, headers=headers)
|
|
||||||
return APIRoot.as_view()
|
return APIRoot.as_view()
|
||||||
|
|
||||||
def register(self, prefix, viewset, base_name=None):
|
def register(self, prefix, viewset, base_name=None):
|
||||||
|
|
|
@ -2,7 +2,7 @@ from rest_framework import viewsets
|
||||||
from rest_framework.decorators import link
|
from rest_framework.decorators import link
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from orchestra.api import router, collectionlink
|
from orchestra.api import router
|
||||||
from orchestra.apps.accounts.api import AccountApiMixin
|
from orchestra.apps.accounts.api import AccountApiMixin
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
@ -19,17 +19,18 @@ class DomainViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
||||||
qs = super(DomainViewSet, self).get_queryset()
|
qs = super(DomainViewSet, self).get_queryset()
|
||||||
return qs.prefetch_related('records')
|
return qs.prefetch_related('records')
|
||||||
|
|
||||||
@collectionlink()
|
|
||||||
def configuration(self, request):
|
|
||||||
names = ['DOMAINS_DEFAULT_A', 'DOMAINS_DEFAULT_MX', 'DOMAINS_DEFAULT_NS']
|
|
||||||
return Response({
|
|
||||||
name: getattr(settings, name, None) for name in names
|
|
||||||
})
|
|
||||||
|
|
||||||
@link()
|
@link()
|
||||||
def view_zone(self, request, pk=None):
|
def view_zone(self, request, pk=None):
|
||||||
domain = self.get_object()
|
domain = self.get_object()
|
||||||
return Response({'zone': domain.render_zone()})
|
return Response({'zone': domain.render_zone()})
|
||||||
|
|
||||||
|
def metadata(self, request):
|
||||||
|
ret = super(DomainViewSet, self).metadata(request)
|
||||||
|
names = ['DOMAINS_DEFAULT_A', 'DOMAINS_DEFAULT_MX', 'DOMAINS_DEFAULT_NS']
|
||||||
|
ret['settings'] = {
|
||||||
|
name.lower(): getattr(settings, name, None) for name in names
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
router.register(r'domains', DomainViewSet)
|
router.register(r'domains', DomainViewSet)
|
||||||
|
|
|
@ -75,6 +75,7 @@ class Bind9MasterDomainBackend(ServiceBackend):
|
||||||
class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
|
class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
|
||||||
verbose_name = _("Bind9 slave domain")
|
verbose_name = _("Bind9 slave domain")
|
||||||
related_models = (('domains.Domain', 'origin'),)
|
related_models = (('domains.Domain', 'origin'),)
|
||||||
|
|
||||||
def save(self, domain):
|
def save(self, domain):
|
||||||
context = self.get_context(domain)
|
context = self.get_context(domain)
|
||||||
self.update_conf(context)
|
self.update_conf(context)
|
||||||
|
|
|
@ -8,12 +8,12 @@ from django.db import models
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
|
|
||||||
from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin#, ChangeViewActions
|
from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin#, ChangeViewActions
|
||||||
from orchestra.admin.utils import (link, colored, wrap_admin_view, display_timesince)
|
from orchestra.admin.utils import (link, colored, wrap_admin_view, display_timesince)
|
||||||
|
from orchestra.apps.contacts import settings as contacts_settings
|
||||||
|
|
||||||
from .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets,
|
from .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets,
|
||||||
mark_as_unread, mark_as_read, set_default_queue)
|
mark_as_unread, mark_as_read, set_default_queue)
|
||||||
|
@ -52,15 +52,15 @@ class MessageReadOnlyInline(admin.TabularInline):
|
||||||
'all': ('orchestra/css/hide-inline-id.css',)
|
'all': ('orchestra/css/hide-inline-id.css',)
|
||||||
}
|
}
|
||||||
|
|
||||||
def content_html(self, obj):
|
def content_html(self, msg):
|
||||||
context = {
|
context = {
|
||||||
'number': obj.number,
|
'number': msg.number,
|
||||||
'time': display_timesince(obj.created_on),
|
'time': display_timesince(msg.created_on),
|
||||||
'author': link('author')(self, obj),
|
'author': link('author')(self, msg) if msg.author else msg.author_name,
|
||||||
}
|
}
|
||||||
summary = _("#%(number)i Updated by %(author)s about %(time)s") % context
|
summary = _("#%(number)i Updated by %(author)s about %(time)s") % context
|
||||||
header = '<strong style="color:#666;">%s</strong><hr />' % summary
|
header = '<strong style="color:#666;">%s</strong><hr />' % summary
|
||||||
content = markdown(obj.content)
|
content = markdown(msg.content)
|
||||||
content = content.replace('>\n', '>')
|
content = content.replace('>\n', '>')
|
||||||
content = '<div style="padding-left:20px;">%s</div>' % content
|
content = '<div style="padding-left:20px;">%s</div>' % content
|
||||||
return header + content
|
return header + content
|
||||||
|
@ -111,8 +111,9 @@ class TicketInline(admin.TabularInline):
|
||||||
owner_link = link('owner')
|
owner_link = link('owner')
|
||||||
|
|
||||||
def ticket_id(self, instance):
|
def ticket_id(self, instance):
|
||||||
return mark_safe('<b>%s</b>' % link()(self, instance))
|
return '<b>%s</b>' % link()(self, instance)
|
||||||
ticket_id.short_description = '#'
|
ticket_id.short_description = '#'
|
||||||
|
ticket_id.allow_tags = True
|
||||||
|
|
||||||
def colored_state(self, instance):
|
def colored_state(self, instance):
|
||||||
return colored('state', STATE_COLORS, bold=False)(instance)
|
return colored('state', STATE_COLORS, bold=False)(instance)
|
||||||
|
@ -165,7 +166,6 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
|
||||||
)
|
)
|
||||||
readonly_fieldsets = (
|
readonly_fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('wide',),
|
|
||||||
'fields': ('display_summary',
|
'fields': ('display_summary',
|
||||||
('display_queue', 'display_owner'),
|
('display_queue', 'display_owner'),
|
||||||
('display_state', 'display_priority'),
|
('display_state', 'display_priority'),
|
||||||
|
@ -174,7 +174,7 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
|
||||||
)
|
)
|
||||||
fieldsets = readonly_fieldsets + (
|
fieldsets = readonly_fieldsets + (
|
||||||
('Update', {
|
('Update', {
|
||||||
'classes': ('collapse', 'wide'),
|
'classes': ('collapse',),
|
||||||
'fields': ('subject',
|
'fields': ('subject',
|
||||||
('queue', 'owner',),
|
('queue', 'owner',),
|
||||||
('state', 'priority'),
|
('state', 'priority'),
|
||||||
|
@ -183,7 +183,6 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
|
||||||
)
|
)
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('wide',),
|
|
||||||
'fields': ('subject',
|
'fields': ('subject',
|
||||||
('queue', 'owner',),
|
('queue', 'owner',),
|
||||||
('state', 'priority'),
|
('state', 'priority'),
|
||||||
|
@ -204,17 +203,21 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
|
||||||
display_owner = link('owner')
|
display_owner = link('owner')
|
||||||
|
|
||||||
def display_summary(self, ticket):
|
def display_summary(self, ticket):
|
||||||
author_url = link('creator')(self, ticket)
|
context = {
|
||||||
created = display_timesince(ticket.created_on)
|
'creator': link('creator')(self, ticket) if ticket.creator else ticket.creator_name,
|
||||||
messages = ticket.messages.order_by('-created_on')
|
'created': display_timesince(ticket.created_on),
|
||||||
updated = ''
|
'updated': '',
|
||||||
if messages:
|
}
|
||||||
updated_on = display_timesince(messages[0].created_on)
|
msg = ticket.messages.last()
|
||||||
updated_by = link('author')(self, messages[0])
|
if msg:
|
||||||
updated = '. Updated by %s about %s' % (updated_by, updated_on)
|
context.update({
|
||||||
msg = '<h4>Added by %s about %s%s</h4>' % (author_url, created, updated)
|
'updated': display_timesince(msg.created_on),
|
||||||
return mark_safe(msg)
|
'updater': link('author')(self, msg) if msg.author else msg.author_name,
|
||||||
|
})
|
||||||
|
context['updated'] = '. Updated by %(updater)s about %(updated)s' % context
|
||||||
|
return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context
|
||||||
display_summary.short_description = 'Summary'
|
display_summary.short_description = 'Summary'
|
||||||
|
display_summary.allow_tags = True
|
||||||
|
|
||||||
def display_priority(self, ticket):
|
def display_priority(self, ticket):
|
||||||
""" State colored for change_form """
|
""" State colored for change_form """
|
||||||
|
@ -302,15 +305,17 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
|
||||||
def message_preview_view(self, request):
|
def message_preview_view(self, request):
|
||||||
""" markdown preview render via ajax """
|
""" markdown preview render via ajax """
|
||||||
data = request.POST.get("data")
|
data = request.POST.get("data")
|
||||||
data_formated = markdown(strip_tags(data))
|
data_formated = markdowt_tn(strip_tags(data))
|
||||||
return HttpResponse(data_formated)
|
return HttpResponse(data_formated)
|
||||||
|
|
||||||
|
def queryset(self, request):
|
||||||
|
""" Order by structured name and imporve performance """
|
||||||
|
qs = super(TicketAdmin, self).queryset(request)
|
||||||
|
return qs.select_related('queue', 'owner', 'creator')
|
||||||
|
|
||||||
|
|
||||||
class QueueAdmin(admin.ModelAdmin):
|
class QueueAdmin(admin.ModelAdmin):
|
||||||
# TODO notify
|
list_display = ['name', 'default', 'num_tickets']
|
||||||
list_display = [
|
|
||||||
'name', 'default', 'num_tickets'
|
|
||||||
]
|
|
||||||
actions = [set_default_queue]
|
actions = [set_default_queue]
|
||||||
inlines = [TicketInline]
|
inlines = [TicketInline]
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
@ -324,9 +329,21 @@ class QueueAdmin(admin.ModelAdmin):
|
||||||
num = queue.tickets.count()
|
num = queue.tickets.count()
|
||||||
url = reverse('admin:issues_ticket_changelist')
|
url = reverse('admin:issues_ticket_changelist')
|
||||||
url += '?my_tickets=False&queue=%i' % queue.pk
|
url += '?my_tickets=False&queue=%i' % queue.pk
|
||||||
return mark_safe('<a href="%s">%d</a>' % (url, num))
|
return '<a href="%s">%d</a>' % (url, num)
|
||||||
num_tickets.short_description = _("Tickets")
|
num_tickets.short_description = _("Tickets")
|
||||||
num_tickets.admin_order_field = 'tickets__count'
|
num_tickets.admin_order_field = 'tickets__count'
|
||||||
|
num_tickets.allow_tags = True
|
||||||
|
|
||||||
|
def get_list_display(self, request):
|
||||||
|
""" show notifications """
|
||||||
|
list_display = list(self.list_display)
|
||||||
|
for value, verbose in contacts_settings.CONTACTS_EMAIL_USAGES:
|
||||||
|
def display_notify(queue, notify=value):
|
||||||
|
return notify in queue.notify
|
||||||
|
display_notify.short_description = verbose
|
||||||
|
display_notify.boolean = True
|
||||||
|
list_display.append(display_notify)
|
||||||
|
return list_display
|
||||||
|
|
||||||
def queryset(self, request):
|
def queryset(self, request):
|
||||||
qs = super(QueueAdmin, self).queryset(request)
|
qs = super(QueueAdmin, self).queryset(request)
|
||||||
|
|
|
@ -16,17 +16,19 @@ class TicketViewSet(viewsets.ModelViewSet):
|
||||||
@action()
|
@action()
|
||||||
def mark_as_read(self, request, pk=None):
|
def mark_as_read(self, request, pk=None):
|
||||||
ticket = self.get_object()
|
ticket = self.get_object()
|
||||||
ticket.mark_as_read()
|
ticket.mark_as_read_by(request.user)
|
||||||
return Response({'status': 'Ticket marked as readed'})
|
return Response({'status': 'Ticket marked as read'})
|
||||||
|
|
||||||
@action()
|
@action()
|
||||||
def mark_as_unread(self, request, pk=None):
|
def mark_as_unread(self, request, pk=None):
|
||||||
ticket = self.get_object()
|
ticket = self.get_object()
|
||||||
ticket.mark_as_unread()
|
ticket.mark_as_unread_by(request.user)
|
||||||
return Response({'status': 'Ticket marked as unreaded'})
|
return Response({'status': 'Ticket marked as unread'})
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super(TicketViewSet, self).get_queryset()
|
qs = super(TicketViewSet, self).get_queryset()
|
||||||
|
qs = qs.select_related('creator', 'queue')
|
||||||
|
qs = qs.prefetch_related('messages__author')
|
||||||
return qs.filter(creator__account=self.request.user.account_id)
|
return qs.filter(creator__account=self.request.user.account_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ class TicketForm(forms.ModelForm):
|
||||||
description = description.replace('>\n', '#Ha9G9-?8')
|
description = description.replace('>\n', '#Ha9G9-?8')
|
||||||
description = description.replace('\n', '<br>')
|
description = description.replace('\n', '<br>')
|
||||||
description = description.replace('#Ha9G9-?8', '>\n')
|
description = description.replace('#Ha9G9-?8', '>\n')
|
||||||
description = '<div style="padding-left: 180px;">%s</div>' % description
|
description = '<div style="padding-left: 95px;">%s</div>' % description
|
||||||
widget = ReadOnlyWidget(description, description)
|
widget = ReadOnlyWidget(description, description)
|
||||||
self.fields['display_description'].widget = widget
|
self.fields['display_description'].widget = widget
|
||||||
|
|
||||||
|
|
|
@ -32,5 +32,9 @@ def get_ticket_changes(modeladmin, request, ticket):
|
||||||
old_value = getattr(ticket, attr)
|
old_value = getattr(ticket, attr)
|
||||||
new_value = form.cleaned_data[attr]
|
new_value = form.cleaned_data[attr]
|
||||||
if old_value != new_value:
|
if old_value != new_value:
|
||||||
|
choices = dict(form.fields[attr].choices)
|
||||||
|
if old_value in choices:
|
||||||
|
old_value = choices[old_value]
|
||||||
|
new_value = choices[new_value]
|
||||||
changes[attr] = (old_value, new_value)
|
changes[attr] = (old_value, new_value)
|
||||||
return changes
|
return changes
|
||||||
|
|
|
@ -57,7 +57,8 @@ class Ticket(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
creator = models.ForeignKey(get_user_model(), verbose_name=_("created by"),
|
creator = models.ForeignKey(get_user_model(), verbose_name=_("created by"),
|
||||||
related_name='tickets_created')
|
related_name='tickets_created', null=True)
|
||||||
|
creator_name = models.CharField(_("creator name"), max_length=256, blank=True)
|
||||||
owner = models.ForeignKey(get_user_model(), null=True, blank=True,
|
owner = models.ForeignKey(get_user_model(), null=True, blank=True,
|
||||||
related_name='tickets_owned', verbose_name=_("assigned to"))
|
related_name='tickets_owned', verbose_name=_("assigned to"))
|
||||||
queue = models.ForeignKey(Queue, related_name='tickets', null=True, blank=True)
|
queue = models.ForeignKey(Queue, related_name='tickets', null=True, blank=True)
|
||||||
|
@ -104,6 +105,8 @@ class Ticket(models.Model):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
""" notify stakeholders of new ticket """
|
""" notify stakeholders of new ticket """
|
||||||
new_issue = not self.pk
|
new_issue = not self.pk
|
||||||
|
if not self.creator_name and self.creator:
|
||||||
|
self.creator_name = self.creator.get_full_name()
|
||||||
super(Ticket, self).save(*args, **kwargs)
|
super(Ticket, self).save(*args, **kwargs)
|
||||||
if new_issue:
|
if new_issue:
|
||||||
# PK should be available for rendering the template
|
# PK should be available for rendering the template
|
||||||
|
@ -119,16 +122,16 @@ class Ticket(models.Model):
|
||||||
return self.cc.split(',') if self.cc else []
|
return self.cc.split(',') if self.cc else []
|
||||||
|
|
||||||
def mark_as_read_by(self, user):
|
def mark_as_read_by(self, user):
|
||||||
TicketTracker.objects.get_or_create(ticket=self, user=user)
|
self.trackers.get_or_create(user=user)
|
||||||
|
|
||||||
def mark_as_unread_by(self, user):
|
def mark_as_unread_by(self, user):
|
||||||
TicketTracker.objects.filter(ticket=self, user=user).delete()
|
self.trackers.filter(user=user).delete()
|
||||||
|
|
||||||
def mark_as_unread(self):
|
def mark_as_unread(self):
|
||||||
TicketTracker.objects.filter(ticket=self).delete()
|
self.trackers.all().delete()
|
||||||
|
|
||||||
def is_read_by(self, user):
|
def is_read_by(self, user):
|
||||||
return TicketTracker.objects.filter(ticket=self, user=user).exists()
|
return self.trackers.filter(user=user).exists()
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
self.state = Ticket.REJECTED
|
self.state = Ticket.REJECTED
|
||||||
|
@ -152,9 +155,13 @@ class Message(models.Model):
|
||||||
related_name='messages')
|
related_name='messages')
|
||||||
author = models.ForeignKey(get_user_model(), verbose_name=_("author"),
|
author = models.ForeignKey(get_user_model(), verbose_name=_("author"),
|
||||||
related_name='ticket_messages')
|
related_name='ticket_messages')
|
||||||
|
author_name = models.CharField(_("author name"), max_length=256, blank=True)
|
||||||
content = models.TextField(_("content"))
|
content = models.TextField(_("content"))
|
||||||
created_on = models.DateTimeField(_("created on"), auto_now_add=True)
|
created_on = models.DateTimeField(_("created on"), auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
get_latest_by = "created_on"
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u"#%i" % self.id
|
return u"#%i" % self.id
|
||||||
|
|
||||||
|
@ -162,7 +169,9 @@ class Message(models.Model):
|
||||||
""" notify stakeholders of ticket update """
|
""" notify stakeholders of ticket update """
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
self.ticket.mark_as_unread()
|
self.ticket.mark_as_unread()
|
||||||
|
self.ticket.mark_as_read_by(self.author)
|
||||||
self.ticket.notify(message=self)
|
self.ticket.notify(message=self)
|
||||||
|
self.author_name = self.author.get_full_name()
|
||||||
super(Message, self).save(*args, **kwargs)
|
super(Message, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -13,8 +13,8 @@ class QueueSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class MessageSerializer(serializers.HyperlinkedModelSerializer):
|
class MessageSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Message
|
model = Message
|
||||||
fields = ('id', 'author', 'content', 'created_on')
|
fields = ('id', 'author', 'author_name', 'content', 'created_on')
|
||||||
read_only_fields = ('author', 'created_on')
|
read_only_fields = ('author', 'author_name', 'created_on')
|
||||||
|
|
||||||
def get_identity(self, data):
|
def get_identity(self, data):
|
||||||
return data.get('id')
|
return data.get('id')
|
||||||
|
@ -32,10 +32,10 @@ class TicketSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = (
|
fields = (
|
||||||
'url', 'id', 'creator', 'owner', 'queue', 'subject', 'description',
|
'url', 'id', 'creator', 'creator_name', 'owner', 'queue', 'subject',
|
||||||
'state', 'messages', 'is_read'
|
'description', 'state', 'messages', 'is_read'
|
||||||
)
|
)
|
||||||
read_only_fields = ('creator', 'owner')
|
read_only_fields = ('creator', 'creator_name', 'owner')
|
||||||
|
|
||||||
def get_is_read(self, obj):
|
def get_is_read(self, obj):
|
||||||
return obj.is_read_by(self.context['request'].user)
|
return obj.is_read_by(self.context['request'].user)
|
||||||
|
|
|
@ -2,17 +2,12 @@ import threading
|
||||||
|
|
||||||
from django import db
|
from django import db
|
||||||
|
|
||||||
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .helpers import send_report
|
from .helpers import send_report
|
||||||
|
|
||||||
|
|
||||||
def get_router():
|
|
||||||
module = '.'.join(settings.ORCHESTRATION_ROUTER.split('.')[:-1])
|
|
||||||
cls = settings.ORCHESTRATION_ROUTER.split('.')[-1]
|
|
||||||
module = __import__(module, fromlist=[module])
|
|
||||||
return getattr(module, cls)
|
|
||||||
|
|
||||||
|
|
||||||
def as_task(execute):
|
def as_task(execute):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
with db.transaction.commit_manually():
|
with db.transaction.commit_manually():
|
||||||
|
@ -37,7 +32,7 @@ def close_connection(execute):
|
||||||
|
|
||||||
def execute(operations):
|
def execute(operations):
|
||||||
""" generates and executes the operations on the servers """
|
""" generates and executes the operations on the servers """
|
||||||
router = get_router()
|
router = import_class(settings.ORCHESTRATION_ROUTER)
|
||||||
# Generate scripts per server+backend
|
# Generate scripts per server+backend
|
||||||
scripts = {}
|
scripts = {}
|
||||||
for operation in operations:
|
for operation in operations:
|
||||||
|
|
|
@ -37,9 +37,8 @@ class User(auth.AbstractBaseUser):
|
||||||
REQUIRED_FIELDS = ['email']
|
REQUIRED_FIELDS = ['email']
|
||||||
|
|
||||||
def get_full_name(self):
|
def get_full_name(self):
|
||||||
""" Returns the first_name plus the last_name, with a space in between """
|
|
||||||
full_name = '%s %s' % (self.first_name, self.last_name)
|
full_name = '%s %s' % (self.first_name, self.last_name)
|
||||||
return full_name.strip()
|
return full_name.strip() or self.username
|
||||||
|
|
||||||
def get_short_name(self):
|
def get_short_name(self):
|
||||||
""" Returns the short name for the user """
|
""" Returns the short name for the user """
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from orchestra.api import router, collectionlink
|
from orchestra.api import router
|
||||||
from orchestra.apps.accounts.api import AccountApiMixin
|
from orchestra.apps.accounts.api import AccountApiMixin
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
@ -14,15 +14,16 @@ class WebAppViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
||||||
serializer_class = WebAppSerializer
|
serializer_class = WebAppSerializer
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
|
|
||||||
@collectionlink()
|
def metadata(self, request):
|
||||||
def configuration(self, request):
|
ret = super(WebAppViewSet, self).metadata(request)
|
||||||
names = [
|
names = [
|
||||||
'WEBAPPS_BASE_ROOT', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS',
|
'WEBAPPS_BASE_ROOT', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS',
|
||||||
'WEBAPPS_PHP_DISABLED_FUNCTIONS', 'WEBAPPS_DEFAULT_TYPE'
|
'WEBAPPS_PHP_DISABLED_FUNCTIONS', 'WEBAPPS_DEFAULT_TYPE'
|
||||||
]
|
]
|
||||||
return Response({
|
ret['settings'] = {
|
||||||
name: getattr(settings, name, None) for name in names
|
name.lower(): getattr(settings, name, None) for name in names
|
||||||
})
|
}
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
router.register(r'webapps', WebAppViewSet)
|
router.register(r'webapps', WebAppViewSet)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from orchestra.api import router, collectionlink
|
from orchestra.api import router
|
||||||
from orchestra.apps.accounts.api import AccountApiMixin
|
from orchestra.apps.accounts.api import AccountApiMixin
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
@ -14,12 +14,13 @@ class WebsiteViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
||||||
serializer_class = WebsiteSerializer
|
serializer_class = WebsiteSerializer
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
|
|
||||||
@collectionlink()
|
def metadata(self, request):
|
||||||
def configuration(self, request):
|
ret = super(WebsiteViewSet, self).metadata(request)
|
||||||
names = ['WEBSITES_OPTIONS', 'WEBSITES_PORT_CHOICES']
|
names = ['WEBSITES_OPTIONS', 'WEBSITES_PORT_CHOICES']
|
||||||
return Response({
|
ret['settings'] = {
|
||||||
name: getattr(settings, name, None) for name in names
|
name.lower(): getattr(settings, name, None) for name in names
|
||||||
})
|
}
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
router.register(r'websites', WebsiteViewSet)
|
router.register(r'websites', WebsiteViewSet)
|
||||||
|
|
|
@ -26,3 +26,5 @@ STOP_SERVICES = getattr(settings, 'STOP_SERVICES',
|
||||||
[('uwsgi', 'nginx'), 'celerybeat', 'celeryd', 'celeryevcam', 'postgresql']
|
[('uwsgi', 'nginx'), 'celerybeat', 'celeryd', 'celeryevcam', 'postgresql']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
API_ROOT_VIEW = getattr(settings, 'API_ROOT_VIEW', 'orchestra.api.root.APIRoot')
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
|
||||||
|
def import_class(cls):
|
||||||
|
module = '.'.join(cls.split('.')[:-1])
|
||||||
|
cls = cls.split('.')[-1]
|
||||||
|
module = __import__(module, fromlist=[module])
|
||||||
|
return getattr(module, cls)
|
||||||
|
|
||||||
|
|
||||||
class OrderedSet(collections.MutableSet):
|
class OrderedSet(collections.MutableSet):
|
||||||
def __init__(self, iterable=None):
|
def __init__(self, iterable=None):
|
||||||
self.end = end = []
|
self.end = end = []
|
||||||
|
|
Loading…
Reference in New Issue