Remove DjrillAdminSite

Closes #78
This commit is contained in:
medmunds
2015-05-14 11:00:52 -07:00
parent a658e12595
commit 99ac099081
21 changed files with 29 additions and 847 deletions

View File

@@ -1,4 +1,3 @@
include README.rst AUTHORS.txt LICENSE
recursive-include djrill/templates *.html
recursive-include djrill *.py
prune djrill/tests

View File

@@ -28,7 +28,6 @@ package. It includes:
* Mandrill-specific extensions like tags, metadata, tracking, and MailChimp templates
* Optional support for Mandrill inbound email and other webhook notifications,
via Django signals
* An optional Django admin interface
Djrill is released under the BSD license. It is tested against Django 1.3--1.8
(including Python 3 with Django 1.6+, and PyPy support with Django 1.5+).

View File

@@ -1,79 +1,10 @@
from django.conf import settings
from django.contrib.admin.sites import AdminSite
from django.utils.text import capfirst
from djrill.exceptions import MandrillAPIError, NotSupportedByMandrillError, removed_in_djrill_2
from djrill.exceptions import MandrillAPIError, NotSupportedByMandrillError
from ._version import *
# This backend was developed against this API endpoint.
# You can override in settings.py, if desired.
MANDRILL_API_URL = getattr(settings, "MANDRILL_API_URL",
"https://mandrillapp.com/api/1.0")
class DjrillAdminSite(AdminSite):
# This was originally adapted from https://github.com/jsocol/django-adminplus.
# If new versions of Django break DjrillAdminSite, it's worth checking to see
# whether django-adminplus has dealt with something similar.
def __init__(self, *args, **kwargs):
removed_in_djrill_2(
"DjrillAdminSite will be removed in Djrill 2.0. "
"You should remove references to it from your code. "
"(All of its data is available in the Mandrill dashboard.)"
)
super(DjrillAdminSite, self).__init__(*args, **kwargs)
index_template = "djrill/index.html"
custom_views = []
custom_urls = []
def register_view(self, path, view, name, display_name=None):
"""Add a custom admin view.
* `path` is the path in the admin where the view will live, e.g.
http://example.com/admin/somepath
* `view` is any view function you can imagine.
* `name` is an optional pretty name for the list of custom views. If
empty, we'll guess based on view.__name__.
"""
self.custom_views.append((path, view, name, display_name))
def register_url(self, path, view, name):
self.custom_urls.append((path, view, name))
def get_urls(self):
"""Add our custom views to the admin urlconf."""
urls = super(DjrillAdminSite, self).get_urls()
try:
from django.conf.urls import include, url
except ImportError:
# Django 1.3
#noinspection PyDeprecation
from django.conf.urls.defaults import include, url
for path, view, name, display_name in self.custom_views:
urls += [
url(r'^%s$' % path, self.admin_view(view), name=name)
]
for path, view, name in self.custom_urls:
urls += [
url(r'^%s$' % path, self.admin_view(view), name=name)
]
return urls
def index(self, request, extra_context=None):
"""Make sure our list of custom views is on the index page."""
if not extra_context:
extra_context = {}
custom_list = [(path, display_name if display_name else
capfirst(view.__name__)) for path, view, name, display_name in
self.custom_views]
# Sort views alphabetically.
custom_list.sort(key=lambda x: x[1])
extra_context.update({
'custom_list': custom_list
})
return super(DjrillAdminSite, self).index(request, extra_context)

View File

@@ -1,17 +0,0 @@
from django.contrib import admin
from djrill.views import (DjrillIndexView, DjrillSendersListView,
DjrillTagListView,
DjrillUrlListView)
# Only try to register Djrill admin views if DjrillAdminSite
# or django-adminplus is in use
if hasattr(admin.site,'register_view'):
admin.site.register_view("djrill/senders/", DjrillSendersListView.as_view(),
"djrill_senders", "senders")
admin.site.register_view("djrill/status/", DjrillIndexView.as_view(),
"djrill_status", "status")
admin.site.register_view("djrill/tags/", DjrillTagListView.as_view(),
"djrill_tags", "tags")
admin.site.register_view("djrill/urls/", DjrillUrlListView.as_view(),
"djrill_urls", "urls")

View File

@@ -1,9 +0,0 @@
<div id="changelist-filter">
<h2>Tools &amp; Info</h2>
<h3>Status</h3>
{% if status %}
<p>Mandrill is <strong>UP</strong></p>
{% else %}
<p>Mandrill is <strong>DOWN</strong></p>
{% endif %}
</div>

View File

@@ -1,18 +0,0 @@
{% extends "admin/index.html" %}
{% block sidebar %}
{{ block.super }}
{% if custom_list %}
<div class="module" style="float: left; width: 498px">
<table style="width: 100%">
<caption>Djrill</caption>
<tbody>
{% for path, name in custom_list %}
<tr><td><a href="{{ path }}">{{ name|capfirst }}</a></td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}

View File

@@ -1,79 +0,0 @@
{% extends "admin/base_site.html" %}
{% load admin_list i18n %}
{% load url from future %}
{% load cycle from djrill_future %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/changelists.css" />
{{ media.css }}
{% if not actions_on_top and not actions_on_bottom %}
<style>
#changelist table thead th:first-child {width: inherit}
</style>
{% endif %}
{% endblock %}
{% block extrahead %}
{{ block.super }}
{{ media.js }}
{% endblock %}
{% block title %} Djrill Senders | {% trans "Django site admin" %}{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../">
{% trans "Home" %}
</a>
&rsaquo;
Djrill
&rsaquo;
Senders
</div>
{% endblock %}
{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
<div class="module filtered" id="changelist">
{% block date_hierarchy %}{% endblock %}
{% block filters %}
{% include "djrill/_status.html" %}
{% endblock %}
{% block result_list %}
{% if objects %}
<div class="results">
<table cellspacing="0" id="result_list">
<thead>
<tr>
{% for header in objects.0.keys %}
<th scope="col">{{ header|capfirst }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for result in objects %}
<tr class="{% cycle 'row1' 'row2' %}">
{% for item in result.values %}
<td>{{ item }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}
{% block pagination %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -1,67 +0,0 @@
{% extends "admin/base_site.html" %}
{% load admin_list i18n %}
{% load url from future %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/changelists.css" />
{{ media.css }}
{% if not actions_on_top and not actions_on_bottom %}
<style>
#changelist table thead th:first-child {width: inherit}
</style>
{% endif %}
{% endblock %}
{% block extrahead %}
{{ block.super }}
{{ media.js }}
{% if action_form %}{% if actions_on_top or actions_on_bottom %}
<script type="text/javascript">
(function($) {
$(document).ready(function($) {
$("tr input.action-select").actions();
});
})(django.jQuery);
</script>
{% endif %}{% endif %}
{% endblock %}
{% block title %} Djrill Status | {% trans "Django site admin" %}{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../">
{% trans "Home" %}
</a>
&rsaquo;
Djrill
&rsaquo;
Status
</div>
{% endblock %}
{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
{% block object-tools %}
{% endblock %}
<div>
{% block search %}{% endblock %}
{% block date_hierarchy %}{% endblock %}
{% block filters %}{% endblock %}
{% block pagination %}{% endblock %}
<dl>
{% for term, value in status.items %}
<dt>{{ term|capfirst }}</dt>
<dd>{{ value }}</dd>
{% endfor %}
</dl>
</div>
</div>
{% endblock %}

View File

@@ -1,91 +0,0 @@
{% extends "admin/base_site.html" %}
{% load admin_list i18n %}
{% load url from future %}
{% load cycle from djrill_future %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/changelists.css" />
{{ media.css }}
{% if not actions_on_top and not actions_on_bottom %}
<style>
#changelist table thead th:first-child {width: inherit}
</style>
{% endif %}
{% endblock %}
{% block extrahead %}
{{ block.super }}
{{ media.js }}
{% endblock %}
{% block title %} Djrill Tags | {% trans "Django site admin" %}{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../">
{% trans "Home" %}
</a>
&rsaquo;
Djrill
&rsaquo;
Tags
</div>
{% endblock %}
{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
<div class="module filtered" id="changelist">
{% block search %} {% endblock %}
{% block date_hierarchy %}{% endblock %}
{% block filters %}
{% include "djrill/_status.html" %}
{% endblock %}
{% block result_list %}
{% if objects %}
<div class="results">
<table cellspacing="0" id="result_list">
<thead>
<tr>
<th scope="col">Tag</th>
<th scope="col">ID</th>
<th scope="col">Sent</th>
<th scope="col">Opens</th>
<th scope="col">Clicks</th>
<th scope="col">Rejects</th>
<th scope="col">Bounces</th>
<th scope="col">Complaints</th>
</tr>
</thead>
<tbody>
{% for result in objects %}
<tr class="{% cycle 'row1' 'row2' %}">
<td>{{ result.tag }}</td>
<td>{{ result.id }}</td>
<td>{{ result.sent }}</td>
<td>{{ result.opens }}</td>
<td>{{ result.clicks }}</td>
<td>{{ result.rejects }}</td>
<td>{{ result.bounces }}</td>
<td>{{ result.complaints }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}
{% block pagination %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -1,81 +0,0 @@
{% extends "admin/base_site.html" %}
{% load admin_list i18n %}
{% load url from future %}
{% load cycle from djrill_future %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/changelists.css" />
{{ media.css }}
{% if not actions_on_top and not actions_on_bottom %}
<style>
#changelist table thead th:first-child {width: inherit}
</style>
{% endif %}
{% endblock %}
{% block extrahead %}
{{ block.super }}
{{ media.js }}
{% endblock %}
{% block title %} Djrill URLs | {% trans "Django site admin" %}{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../">
{% trans "Home" %}
</a>
&rsaquo;
Djrill
&rsaquo;
URLs
</div>
{% endblock %}
{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
<div class="module filtered" id="changelist">
{% block search %}{% endblock %}
{% block date_hierarchy %}{% endblock %}
{% block filters %}
{% include "djrill/_status.html" %}
{% endblock %}
{% block result_list %}
{% if objects %}
<div class="results">
<table cellspacing="0" id="result_list">
<thead>
<tr>
{% for header in objects.0.keys %}
<th scope="col">{{ header|capfirst }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for result in objects %}
<tr class="{% cycle 'row1' 'row2' %}">
{% for item in result.values %}
<td>{{ item }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}
{% block pagination %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -1,16 +0,0 @@
# Future templatetags library that is also backwards compatible with
# older versions of Django (so long as Djrill's code is compatible
# with the future behavior).
from django import template
# Django 1.8 changes autoescape behavior in cycle tag.
# Djrill has been compatible with future behavior all along.
try:
from django.templatetags.future import cycle
except ImportError:
from django.template.defaulttags import cycle
register = template.Library()
register.tag(cycle)

View File

@@ -1,4 +1,3 @@
from djrill.tests.test_admin import *
from djrill.tests.test_legacy import *
from djrill.tests.test_mandrill_send import *
from djrill.tests.test_mandrill_send_template import *

View File

@@ -1,18 +0,0 @@
try:
from django.conf.urls import include, url
except ImportError:
# Django 1.3
from django.conf.urls.defaults import include, url
from django.contrib import admin
from djrill import DjrillAdminSite
# Set up the DjrillAdminSite as suggested in the docs
admin.site = DjrillAdminSite()
admin.autodiscover()
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
]

View File

@@ -1,155 +0,0 @@
import sys
import warnings
from django.test import TestCase
from django.contrib.auth.models import User
from django.contrib import admin
import six
from djrill.exceptions import RemovedInDjrill2
from djrill.tests.mock_backend import DjrillBackendMockAPITestCase
from djrill.tests.utils import override_settings
def reset_admin_site():
"""Return the Django admin globals to their original state"""
admin.site = admin.AdminSite() # restore default
if 'djrill.admin' in sys.modules:
del sys.modules['djrill.admin'] # force autodiscover to re-import
@override_settings(ROOT_URLCONF='djrill.tests.admin_urls')
class DjrillAdminTests(DjrillBackendMockAPITestCase):
"""Test the Djrill admin site"""
@classmethod
def setUpClass(cls):
super(DjrillAdminTests, cls).setUpClass()
# Other test cases may muck with the Django admin site globals,
# so return it to the default state before loading test_admin_urls
reset_admin_site()
def run(self, result=None):
with warnings.catch_warnings():
# DjrillAdminSite deprecation is tested in test_legacy
warnings.filterwarnings('ignore', category=RemovedInDjrill2,
message="DjrillAdminSite will be removed in Djrill 2.0")
# We don't care that the `cycle` template tag will be removed in Django 2.0,
# because we're planning to drop the Djrill admin templates before then.
warnings.filterwarnings('ignore', category=PendingDeprecationWarning,
message="Loading the `cycle` tag from the `future` library")
# We don't care that user messaging was deprecated in Django 1.3
# (testing artifact of our runtests.py minimal Django settings)
warnings.filterwarnings('ignore', category=DeprecationWarning,
message="The user messaging API is deprecated.")
super(DjrillAdminTests, self).run(result)
def setUp(self):
super(DjrillAdminTests, self).setUp()
# Must be authenticated staff to access admin site...
admin = User.objects.create_user('admin', 'admin@example.com', 'secret')
admin.is_staff = True
admin.save()
self.client.login(username='admin', password='secret')
def test_admin_senders(self):
self.mock_post.return_value = self.MockResponse(raw=self.mock_api_content['users/senders.json'])
response = self.client.get('/admin/djrill/senders/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Senders")
self.assertContains(response, "sender.example@mandrillapp.com")
def test_admin_status(self):
self.mock_post.return_value = self.MockResponse(raw=self.mock_api_content['users/info.json'])
response = self.client.get('/admin/djrill/status/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Status")
self.assertContains(response, "myusername")
def test_admin_tags(self):
self.mock_post.return_value = self.MockResponse(raw=self.mock_api_content['tags/list.json'])
response = self.client.get('/admin/djrill/tags/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Tags")
self.assertContains(response, "example-tag")
def test_admin_urls(self):
self.mock_post.return_value = self.MockResponse(raw=self.mock_api_content['urls/list.json'])
response = self.client.get('/admin/djrill/urls/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, "URLs")
self.assertContains(response, "example.com/example-page")
def test_admin_index(self):
"""Make sure Djrill section is included in the admin index page"""
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Djrill")
mock_api_content = {
'users/senders.json': six.b('''
[
{
"address": "sender.example@mandrillapp.com",
"created_at": "2013-01-01 15:30:27",
"sent": 42, "hard_bounces": 42, "soft_bounces": 42, "rejects": 42, "complaints": 42,
"unsubs": 42, "opens": 42, "clicks": 42, "unique_opens": 42, "unique_clicks": 42
}
]
'''),
'users/info.json': six.b('''
{
"username": "myusername",
"created_at": "2013-01-01 15:30:27",
"public_id": "aaabbbccc112233",
"reputation": 42,
"hourly_quota": 42,
"backlog": 42,
"stats": {
"today": { "sent": 42, "hard_bounces": 42, "soft_bounces": 42, "rejects": 42, "complaints": 42,
"unsubs": 42, "opens": 42, "unique_opens": 42, "clicks": 42, "unique_clicks": 42 },
"last_7_days": { "sent": 42, "hard_bounces": 42, "soft_bounces": 42, "rejects": 42, "complaints": 42,
"unsubs": 42, "opens": 42, "unique_opens": 42, "clicks": 42, "unique_clicks": 42 },
"last_30_days": { "sent": 42, "hard_bounces": 42, "soft_bounces": 42, "rejects": 42, "complaints": 42,
"unsubs": 42, "opens": 42, "unique_opens": 42, "clicks": 42, "unique_clicks": 42 },
"last_60_days": { "sent": 42, "hard_bounces": 42, "soft_bounces": 42, "rejects": 42, "complaints": 42,
"unsubs": 42, "opens": 42, "unique_opens": 42, "clicks": 42, "unique_clicks": 42 },
"last_90_days": { "sent": 42, "hard_bounces": 42, "soft_bounces": 42, "rejects": 42, "complaints": 42,
"unsubs": 42, "opens": 42, "unique_opens": 42, "clicks": 42, "unique_clicks": 42 },
"all_time": { "sent": 42, "hard_bounces": 42, "soft_bounces": 42, "rejects": 42, "complaints": 42,
"unsubs": 42, "opens": 42, "unique_opens": 42, "clicks": 42, "unique_clicks": 42 }
}
}
'''),
'tags/list.json': six.b('''
[
{
"tag": "example-tag",
"reputation": 42,
"sent": 42, "hard_bounces": 42, "soft_bounces": 42, "rejects": 42, "complaints": 42,
"unsubs": 42, "opens": 42, "clicks": 42, "unique_opens": 42, "unique_clicks": 42
}
]
'''),
'urls/list.json': six.b('''
[
{
"url": "http://example.com/example-page",
"sent": 42,
"clicks": 42,
"unique_clicks": 42
}
]
'''),
}
class DjrillNoAdminTests(TestCase):
def test_admin_autodiscover_without_djrill(self):
"""Make sure autodiscover doesn't die without DjrillAdminSite"""
reset_admin_site()
admin.autodiscover() # test: this shouldn't error

View File

@@ -6,7 +6,7 @@ import warnings
from django.core import mail
from django.test import TestCase
from djrill import MandrillAPIError, NotSupportedByMandrillError, DjrillAdminSite
from djrill import MandrillAPIError, NotSupportedByMandrillError
from djrill.exceptions import RemovedInDjrill2
from djrill.mail import DjrillMessage
from djrill.tests.mock_backend import DjrillBackendMockAPITestCase
@@ -19,12 +19,6 @@ class DjrillBackendDeprecationTests(DjrillBackendMockAPITestCase):
reset_warning_registry()
super(DjrillBackendDeprecationTests, self).setUp()
def test_deprecated_admin_site(self):
"""Djrill 2.0 drops the custom DjrillAdminSite"""
self.assertWarnsMessage(DeprecationWarning,
"DjrillAdminSite will be removed in Djrill 2.0",
DjrillAdminSite)
def test_deprecated_json_date_encoding(self):
"""Djrill 2.0+ avoids a blanket JSONDateUTCEncoder"""
# Djrill allows dates for send_at, so shouldn't warn:

View File

@@ -2,88 +2,15 @@ from base64 import b64encode
import hashlib
import hmac
import json
from django import forms
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.views.generic import TemplateView, View
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
import requests
from djrill import MANDRILL_API_URL, signals
from .compat import b
class DjrillAdminMedia(object):
def _media(self):
js = ["js/core.js", "js/jquery.min.js", "js/jquery.init.js"]
return forms.Media(js=["%s%s" % (settings.STATIC_URL, url) for url in js])
media = property(_media)
class DjrillApiMixin(object):
"""
Simple Mixin to grab the api info from the settings file.
"""
def __init__(self):
self.api_key = getattr(settings, "MANDRILL_API_KEY", None)
self.api_url = MANDRILL_API_URL
if not self.api_key:
raise ImproperlyConfigured(
"You have not set your mandrill api key in the settings file.")
def get_context_data(self, **kwargs):
kwargs = super(DjrillApiMixin, self).get_context_data(**kwargs)
status = False
req = requests.post("%s/%s" % (self.api_url, "users/ping.json"),
data={"key": self.api_key})
if req.status_code == 200:
status = True
kwargs.update({"status": status})
return kwargs
class DjrillApiJsonObjectsMixin(object):
"""
Mixin to grab json objects from the api.
"""
api_uri = None
def get_api_uri(self):
if self.api_uri is None:
raise NotImplementedError(
"%(cls)s is missing an api_uri. "
"Define %(cls)s.api_uri or override %(cls)s.get_api_uri()." % {
"cls": self.__class__.__name__
})
def get_json_objects(self, extra_dict=None, extra_api_uri=None):
request_dict = {"key": self.api_key}
if extra_dict:
request_dict.update(extra_dict)
payload = json.dumps(request_dict)
api_uri = extra_api_uri or self.api_uri
req = requests.post("%s/%s" % (self.api_url, api_uri),
data=payload)
if req.status_code == 200:
return req.text
messages.error(self.request, self._api_error_handler(req))
return json.dumps("error")
def _api_error_handler(self, req):
"""
If the API returns an error, display it to the user.
"""
content = json.loads(req.text)
return "Mandrill returned a %d response: %s" % (req.status_code,
content["message"])
from djrill import signals
from djrill.compat import b
class DjrillWebhookSecretMixin(object):
@@ -139,66 +66,6 @@ class DjrillWebhookSignatureMixin(object):
request, *args, **kwargs)
class DjrillIndexView(DjrillApiMixin, TemplateView):
template_name = "djrill/status.html"
def get(self, request, *args, **kwargs):
payload = json.dumps({"key": self.api_key})
req = requests.post("%s/users/info.json" % self.api_url, data=payload)
return self.render_to_response({"status": json.loads(req.text)})
class DjrillSendersListView(DjrillAdminMedia, DjrillApiMixin,
DjrillApiJsonObjectsMixin, TemplateView):
api_uri = "users/senders.json"
template_name = "djrill/senders_list.html"
def get(self, request, *args, **kwargs):
objects = self.get_json_objects()
context = self.get_context_data()
context.update({
"objects": json.loads(objects),
"media": self.media,
})
return self.render_to_response(context)
class DjrillTagListView(DjrillAdminMedia, DjrillApiMixin,
DjrillApiJsonObjectsMixin, TemplateView):
api_uri = "tags/list.json"
template_name = "djrill/tags_list.html"
def get(self, request, *args, **kwargs):
objects = self.get_json_objects()
context = self.get_context_data()
context.update({
"objects": json.loads(objects),
"media": self.media,
})
return self.render_to_response(context)
class DjrillUrlListView(DjrillAdminMedia, DjrillApiMixin,
DjrillApiJsonObjectsMixin, TemplateView):
api_uri = "urls/list.json"
template_name = "djrill/urls_list.html"
def get(self, request, *args, **kwargs):
objects = self.get_json_objects()
context = self.get_context_data()
context.update({
"objects": json.loads(objects),
"media": self.media
})
return self.render_to_response(context)
class DjrillWebhookView(DjrillWebhookSecretMixin, DjrillWebhookSignatureMixin, View):
def head(self, request, *args, **kwargs):
return HttpResponse()

View File

@@ -18,19 +18,28 @@ that will change. (Warnings appear in the console when running Django
in debug mode.)
**Djrill Admin site**
Breaking Changes in Djrill 2.0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Djrill 2.0 will remove the custom Djrill admin site. It duplicates
information from Mandrill's dashboard, most Djrill users are unaware
it exists, and it has caused problems tracking Django admin changes.
Removed DjrillAdminSite
Earlier versions of Djrill included a custom Django admin site.
The equivalent functionality is available in Mandrill's dashboard.
Drill 1.4 will report a DeprecationWarning when you try to load
the `DjrillAdminSite`. You should remove it from your code.
You should remove any references to DjrillAdminSite from your
:file:`urls.py`. E.g.::
Also, if you changed Django's :setting:`INSTALLED_APPS` setting to use
`'django.contrib.admin.apps.SimpleAdminConfig'`, you may be able to
switch that back to `'django.contrib.admin'` and let Django
handle the admin.autodiscover() for you.
.. code-block:: python
# Remove these:
from djrill import DjrillAdminSite
admin.site = DjrillAdminSite()
Also, on Django 1.7 or later if you had switched your :setting:`INSTALLED_APPS`
(in :file:`settings.py`) to use ``'django.contrib.admin.apps.SimpleAdminConfig'``
you *may* want to switch back to the default ``'django.contrib.admin'``
and remove the call to ``admin.autodiscover()`` in your :file:`urls.py`.
(Do this only if you changed to SimpleAdminConfig for Djrill, and aren't
creating custom admin sites for any other Django apps you use.)
**Dates in merge data and other attributes**

View File

@@ -36,9 +36,7 @@ Thanks
------
Thanks to the MailChimp team for asking us to build this nifty little app, and to all of Djrill's
:doc:`contributors <contributing>`. Also thanks to James Socol on Github for his django-adminplus_
library that got us off on the right foot for the custom admin views.
:doc:`contributors <contributing>`.
Oh, and, of course, Kenneth Reitz for the awesome requests_ library.
.. _requests: http://docs.python-requests.org
.. _django-adminplus: https://github.com/jsocol/django-adminplus

View File

@@ -65,47 +65,3 @@ with :ref:`Mandrill-specific sending options <mandrill-send-support>`.)
.. _subaccounts: http://help.mandrill.com/entries/25523278-What-are-subaccounts-
Admin (Optional)
----------------
Djrill includes an optional Django admin interface, which allows you to:
* Check the status of your Mandrill API connection
* See stats on email senders, tags and urls
If you want to enable the Djrill admin interface, edit your base :file:`urls.py`:
.. code-block:: python
:emphasize-lines: 4,6
...
from django.contrib import admin
from djrill import DjrillAdminSite
admin.site = DjrillAdminSite()
admin.autodiscover()
...
urlpatterns = [
...
url(r'^admin/', include(admin.site.urls)),
]
If you are on **Django 1.7 or later,** you will also need to change the config used
by the django.contrib.admin app in your :file:`settings.py`:
.. code-block:: python
:emphasize-lines: 4
...
INSTALLED_APPS = (
# For Django 1.7+, use SimpleAdminConfig because we'll call autodiscover...
'django.contrib.admin.apps.SimpleAdminConfig', # instead of 'django.contrib.admin'
...
'djrill',
...
)
...

View File

@@ -3,13 +3,9 @@
# python runtests.py
import sys
from django import VERSION as django_version
from django.conf import settings
APP = 'djrill'
ADMIN = 'django.contrib.admin'
if django_version >= (1, 7):
ADMIN = 'django.contrib.admin.apps.SimpleAdminConfig'
settings.configure(
DEBUG=True,
@@ -23,7 +19,7 @@ settings.configure(
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
ADMIN,
'django.contrib.admin',
APP,
),
MIDDLEWARE_CLASSES=(
@@ -33,25 +29,10 @@ settings.configure(
'django.contrib.auth.middleware.AuthenticationMiddleware',
),
TEMPLATES=[
# Django 1.8 starter-project template settings
# (needed for test_admin)
# Djrill doesn't have any templates, but tests need a TEMPLATES
# setting to avoid warnings from the Django 1.8+ test client.
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
# insert your TEMPLATE_DIRS here
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
},
},
],
)