├── mobileadmin
├── models.py
├── conf
│ ├── __init__.py
│ └── settings.py
├── templatetags
│ ├── __init__.py
│ ├── mobile_admin_media.py
│ ├── mobile_admin_modify.py
│ └── mobile_admin_list.py
├── media
│ ├── img
│ │ ├── arrow.png
│ │ ├── nav-bg.gif
│ │ ├── search.png
│ │ ├── default-bg.gif
│ │ ├── nav-bg-reverse.gif
│ │ └── default-bg-reverse.gif
│ ├── js
│ │ ├── mobile_safari.js
│ │ └── urlify.js
│ └── css
│ │ └── mobile_safari.css
├── templates
│ └── mobileadmin
│ │ └── mobile_safari
│ │ ├── edit_inline
│ │ ├── tabular.html
│ │ └── stacked.html
│ │ ├── filter.html
│ │ ├── registration
│ │ ├── password_change_done.html
│ │ ├── logged_out.html
│ │ └── password_change_form.html
│ │ ├── 404.html
│ │ ├── invalid_setup.html
│ │ ├── 505.html
│ │ ├── pagination.html
│ │ ├── app_index.html
│ │ ├── includes
│ │ └── fieldset.html
│ │ ├── auth
│ │ └── user
│ │ │ └── add_form.html
│ │ ├── object_history.html
│ │ ├── search_form.html
│ │ ├── login.html
│ │ ├── delete_confirmation.html
│ │ ├── change_list.html
│ │ ├── base.html
│ │ ├── index.html
│ │ └── change_form.html
├── context_processors.py
├── urls.py
├── utils.py
├── options.py
├── __init__.py
├── sites.py
├── decorators.py
└── views.py
├── .gitignore
├── MANIFEST.in
├── README.rst
├── INSTALL.rst
├── setup.py
├── LICENSE.txt
└── docs
└── overview.rst
/mobileadmin/models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mobileadmin/conf/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mobileadmin/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .DS_Store
3 | dist/*
4 | MANIFEST
--------------------------------------------------------------------------------
/mobileadmin/media/img/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jezdez/django-mobileadmin/HEAD/mobileadmin/media/img/arrow.png
--------------------------------------------------------------------------------
/mobileadmin/media/img/nav-bg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jezdez/django-mobileadmin/HEAD/mobileadmin/media/img/nav-bg.gif
--------------------------------------------------------------------------------
/mobileadmin/media/img/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jezdez/django-mobileadmin/HEAD/mobileadmin/media/img/search.png
--------------------------------------------------------------------------------
/mobileadmin/media/img/default-bg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jezdez/django-mobileadmin/HEAD/mobileadmin/media/img/default-bg.gif
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/edit_inline/tabular.html:
--------------------------------------------------------------------------------
1 | {% include "mobileadmin/mobile_safari/edit_inline/stacked.html" %}
--------------------------------------------------------------------------------
/mobileadmin/media/img/nav-bg-reverse.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jezdez/django-mobileadmin/HEAD/mobileadmin/media/img/nav-bg-reverse.gif
--------------------------------------------------------------------------------
/mobileadmin/media/img/default-bg-reverse.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jezdez/django-mobileadmin/HEAD/mobileadmin/media/img/default-bg-reverse.gif
--------------------------------------------------------------------------------
/mobileadmin/context_processors.py:
--------------------------------------------------------------------------------
1 | from mobileadmin.utils import get_user_agent
2 |
3 | def user_agent(request):
4 | return {'user_agent': get_user_agent(request)}
5 |
--------------------------------------------------------------------------------
/mobileadmin/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 | import mobileadmin
3 |
4 | urlpatterns = patterns('',
5 | (r'^(.*)', mobileadmin.site.root),
6 | )
7 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include INSTALL.rst
2 | include LICENSE.txt
3 | include MANIFEST.in
4 | include README.rst
5 | recursive-include docs *
6 | recursive-include mobileadmin/templates *
7 | recursive-include mobileadmin/media *
8 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ===================
2 | Django Mobile Admin
3 | ===================
4 |
5 | This is a drop-in adminstration application for use with Django and a
6 | series of mobile devices and platforms. It requires a recent Django SVN
7 | checkout or Django 1.0.
8 |
9 | For more installation instructions please have a look at INSTALL.rst.
10 |
11 | Cheers,
12 | Jannis/jezdez
13 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/filter.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
{% blocktrans with title|escape as filter_title %} By {{ filter_title }} {% endblocktrans %}
3 | {% for choice in choices %}
4 |
5 |
6 | {{ choice.display|escape }}
7 |
8 |
9 | {% endfor %}
10 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/registration/password_change_done.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/registration/password_change_form.html" %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans 'Password change successful' %}{% endblock %}
5 |
6 | {% block content %}
7 |
8 |
{% trans 'Your password was changed.' %}
9 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/mobileadmin/templatetags/mobile_admin_media.py:
--------------------------------------------------------------------------------
1 | from django.template import Library
2 |
3 | register = Library()
4 |
5 | def mobileadmin_media_prefix():
6 | """
7 | Returns the string contained in the setting MOBILEADMIN_MEDIA_PREFIX.
8 | """
9 | try:
10 | from mobileadmin.conf import settings
11 | except ImportError:
12 | return ''
13 | return settings.MEDIA_PREFIX
14 | mobileadmin_media_prefix = register.simple_tag(mobileadmin_media_prefix)
15 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/404.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans 'Page not found' %}{% endblock %}
5 | {% block breadcrumbs %}{% trans 'Page not found' %}{% endblock %}
6 | {% block tools %}{% endblock tools %}
7 |
8 | {% block content %}
9 |
10 |
{% trans "We're sorry, but the requested page could not be found." %}
11 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/invalid_setup.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n %}
3 |
4 | {% block title %}{{ title }}{% endblock %}
5 |
6 | {% block content %}
7 |
8 |
{% trans "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." %}
9 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/registration/logged_out.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n %}
3 |
4 | {% block breadcrumbs %}{% trans 'Log in again' %} {% trans "Logged out" %} {% endblock %}
5 |
6 | {% block content %}
7 |
8 |
{% trans "Thanks for spending some quality time with the Web site today." %}
9 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/mobileadmin/utils.py:
--------------------------------------------------------------------------------
1 | import re
2 | from mobileadmin.conf import settings
3 |
4 | def get_user_agent(request):
5 | """
6 | Checks if the given user agent string matches one of the valid user
7 | agents.
8 | """
9 | name = request.META.get('HTTP_USER_AGENT', None)
10 | if not name:
11 | return False
12 | for platform, regex in settings.USER_AGENTS.iteritems():
13 | if re.compile(regex).search(name) is not None:
14 | return platform.lower()
15 | return False
16 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/505.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans 'Server error (500)' %}{% endblock %}
5 | {% block breadcrumbs %}{% trans 'Server error (500)' %}{% endblock %}
6 | {% block tools %}{% endblock tools %}
7 |
8 | {% block content %}
9 |
10 |
{% trans "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." %}
11 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/pagination.html:
--------------------------------------------------------------------------------
1 | {% load mobile_admin_list i18n %}
2 |
3 |
4 | {% if pagination_required %}
5 | {% for i in page_range %}{% paginator_number cl i %}{% endfor %}
6 | {% endif %}
7 |
8 | {% if show_all_url %}
9 | {% trans 'All' %}
10 | {% endif %}
11 | {{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|capfirst }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %}
12 |
13 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/app_index.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/index.html" %}
2 | {% load i18n %}
3 |
4 | {% block breadcrumbs %}{{ title }} {% endblock %}
5 |
6 | {% block content %}
7 |
8 | {% for app in app_list %}
9 | {% for model in app.models %}
10 | {% if model.perms.change %}
11 | {{ model.name }}
12 | {% else %}
13 | {{ model.name }}
14 | {% endif %}
15 | {% endfor %}
16 | {% endfor %}
17 |
18 |
19 | {% endblock %}
--------------------------------------------------------------------------------
/INSTALL.rst:
--------------------------------------------------------------------------------
1 | To install django-mobileadmin, run the following command inside this directory:
2 |
3 | python setup.py install
4 |
5 | Or if you'd prefer you can simply place the included ``mobileadmin``
6 | directory somewhere on your Python path, or symlink to it from
7 | somewhere on your Python path; this is useful if you're working from a
8 | Subversion checkout.
9 |
10 | Please see the `Installation` paragraph in docs/overview.txt for usage details.
11 |
12 | Note that this application requires Python 2.3 or later, and a recent
13 | Subversion checkout of Django. You can obtain Python from
14 | http://www.python.org/ and Django from http://www.djangoproject.com/.
15 |
16 | This install notice was bluntly stolen from James Bennett's registration
17 | package, http://code.google.com/p/django-registration/
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/includes/fieldset.html:
--------------------------------------------------------------------------------
1 | {% if fieldset.name %}{{ fieldset.name }} {% endif %}
2 |
3 | {% for line in fieldset %}
4 |
17 | {% endfor %}
18 |
19 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/auth/user/add_form.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/change_form.html" %}
2 | {% load i18n %}
3 |
4 | {% block after_field_sets %}
5 | {% trans "First, enter a username and password. Then, you'll be able to edit more user options." %}
6 |
7 |
8 | {{ form.username.errors }}
9 | {% trans 'Username' %}: {{ form.username }}
10 |
11 |
12 |
13 | {{ form.password1.errors }}
14 | {% trans 'Password' %}: {{ form.password1 }}
15 |
16 |
17 |
18 | {% trans 'Password (again)' %}: {{ form.password2 }}
19 | {{ form.password2.errors }}
20 |
21 |
22 |
23 |
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/edit_inline/stacked.html:
--------------------------------------------------------------------------------
1 | {% load i18n mobile_admin_modify %}
2 |
3 | {{ inline_admin_formset.opts.verbose_name_plural|title }}
4 | {{ inline_admin_formset.formset.management_form }}
5 | {{ inline_admin_formset.formset.non_form_errors }}
6 | {% for inline_admin_form in inline_admin_formset %}
7 | {{ inline_admin_formset.opts.verbose_name|title }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %} #{{ forloop.counter }}{% endif %}
8 | {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}
9 |
10 | {{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}
11 |
12 | {% endif %}
13 | {% for fieldset in inline_admin_form %}
14 | {% mobile_inline_admin_fieldset fieldset user_agent %}
15 | {% endfor %}
16 | {{ inline_admin_form.pk_field.field }}
17 | {% endfor %}
18 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/object_history.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load mobile_admin_modify i18n %}
3 |
4 | {% block breadcrumbs %}{{ object|escape }} {% trans 'History' %}
5 |
6 | {% endblock %}
7 |
8 | {% block content %}
9 | {% if action_list %}
10 |
11 | {% for action in action_list %}
12 |
13 | {{ action.user.username }}, {{ action.action_time|date:_("DATETIME_FORMAT") }}
14 | {{ action.change_message }}
15 |
16 | {% endfor %}
17 |
18 | {% else %}
19 |
20 |
{% trans "This object doesn't have a change history. It probably wasn't added via this admin site." %}
21 |
22 | {% endif %}
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/search_form.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% if cl.search_fields %}
3 | {% trans "Search" %}
4 |
5 |
11 |
12 |
13 | {% trans 'Go' %}
14 | {% if show_result_count %}
15 | {% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}
16 |
17 | {% endif %}
18 |
19 |
20 | {% endif %}
21 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/login.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n %}
3 |
4 | {% block breadcrumbs %}{% trans 'Log in' %}{% endblock %}
5 |
6 | {% block content %}
7 |
26 |
27 |
30 |
31 | {% endblock %}
32 |
--------------------------------------------------------------------------------
/mobileadmin/conf/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | from django.conf import settings
3 |
4 | # PLEASE: Don't change anything here, use your site settings.py
5 |
6 | MEDIA_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', 'media')
7 | MEDIA_PREFIX = getattr(settings, 'MOBILEADMIN_MEDIA_PREFIX', '/mobileadmin_media/')
8 | MEDIA_REGEX = r'^%s(?P.*)$' % MEDIA_PREFIX.lstrip('/')
9 |
10 | USER_AGENTS = {
11 | 'mobile_safari': r'AppleWebKit/.*Mobile/',
12 | 'blackberry': r'^BlackBerry',
13 | 'opera_mini': r'[Oo]pera [Mm]ini',
14 | }
15 | USER_AGENTS.update(getattr(settings, 'MOBILEADMIN_USER_AGENTS', {}))
16 |
17 | TEMPLATE_MAPPING = {
18 | 'index': ('index_template', 'index.html'),
19 | 'display_login_form': ('login_template', 'login.html'),
20 | 'app_index': ('app_index_template', 'app_index.html'),
21 | 'render_change_form': ('change_form_template', 'change_form.html'),
22 | 'changelist_view': ('change_list_template', 'change_list.html'),
23 | 'delete_view': ('delete_confirmation_template', 'delete_confirmation.html'),
24 | 'history_view': ('object_history_template', 'object_history.html'),
25 | 'logout': ('logout_template', 'registration/logged_out.html'),
26 | 'password_change': ('password_change_template', 'registration/password_change_form.html'),
27 | 'password_change_done': ('password_change_done_template', 'registration/password_change_done.html'),
28 | }
29 | TEMPLATE_MAPPING.update(getattr(settings, 'MOBILEADMIN_TEMPLATE_MAPPING', {}))
30 |
--------------------------------------------------------------------------------
/mobileadmin/options.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin import options
2 | from mobileadmin import decorators
3 |
4 | class MobileModelAdmin(options.ModelAdmin):
5 | """
6 | A custom model admin class to override the used templates depending on the
7 | user agent of the request.
8 |
9 | Please use it in case you want to create you own mobileadmin.
10 | """
11 | def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
12 | return super(MobileModelAdmin, self).render_change_form(request, context, add, change, form_url, obj)
13 | render_change_form = decorators.mobile_templates(render_change_form)
14 |
15 | def changelist_view(self, request, extra_context=None):
16 | return super(MobileModelAdmin, self).changelist_view(request, extra_context)
17 | changelist_view = decorators.mobile_templates(changelist_view)
18 |
19 | def delete_view(self, request, object_id, extra_context=None):
20 | return super(MobileModelAdmin, self).delete_view(request, object_id, extra_context)
21 | delete_view = decorators.mobile_templates(delete_view)
22 |
23 | def history_view(self, request, object_id, extra_context=None):
24 | return super(MobileModelAdmin, self).history_view(request, object_id, extra_context)
25 | history_view = decorators.mobile_templates(history_view)
26 |
27 | class MobileStackedInline(options.StackedInline):
28 | template = 'edit_inline/stacked.html'
29 |
30 | class MobileTabularInline(options.InlineModelAdmin):
31 | template = 'edit_inline/tabular.html'
32 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/delete_confirmation.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n %}
3 |
4 | {% block breadcrumbs %}{{ object|escape }} {% trans "Delete" %} {% endblock %}
5 |
6 | {% block content %}
7 | {% if perms_lacking %}
8 |
9 |
{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}
10 |
11 | {% for obj in perms_lacking %}
12 |
13 | {{ obj }}
14 |
15 | {% endfor %}
16 |
17 |
18 | {% else %}
19 |
31 | {% endif %}
32 |
33 | {% endblock %}
34 |
--------------------------------------------------------------------------------
/mobileadmin/__init__.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin import site as main_site
2 | from django.core.exceptions import ImproperlyConfigured
3 |
4 | from mobileadmin import decorators, views
5 | from mobileadmin.conf import settings
6 |
7 | ### From http://www2.lib.uchicago.edu/keith/courses/python/class/5/#attrref
8 | def classlookup(C, name):
9 | if C.__dict__.has_key(name):
10 | return (1, C.__dict__[name])
11 | else:
12 | for b in C.__bases__:
13 | success, value = classlookup(b, name)
14 | if success:
15 | return (1, value)
16 | else:
17 | pass
18 | else:
19 | return (0, None)
20 |
21 |
22 | def autoregister():
23 | """
24 | Auto-register all ModelAdmin instances of the default AdminSite with the
25 | mobileadmin app and set the templates accordingly.
26 | """
27 | from django.contrib.auth.admin import UserAdmin
28 | from mobileadmin.sites import site
29 |
30 | for model, modeladmin in main_site._registry.iteritems():
31 | admin_class = modeladmin.__class__
32 | for name in settings.TEMPLATE_MAPPING:
33 | (found, value) = classlookup(admin_class, name)
34 | if found:
35 | setattr(admin_class, name, decorators.mobile_templates(value))
36 |
37 | if admin_class == UserAdmin:
38 | setattr(admin_class, 'add_view', views.auth_add_view)
39 |
40 | site.register(model, admin_class)
41 |
42 | def autodiscover():
43 | raise ImproperlyConfigured("Please use the autodiscover function of "
44 | "Django's default admin app and then "
45 | "call 'mobileadmin.autoregister' to use "
46 | "mobileadmin.")
47 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/change_list.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load mobile_admin_list i18n mobile_admin_media %}
3 |
4 | {% block breadcrumbs %}
5 | {{ app_label.title|escape }}
6 | {% trans "Add" %}
7 | {% if cl.search_fields or cl.has_filters %}
8 |
9 |
10 |
11 | {% endif %}
12 | {% endblock %}
13 |
14 | {% block content %}
15 |
16 | {{ cl.opts.verbose_name_plural|capfirst|escape }}
17 | {% for res in cl.result_list %}
18 | {{ res|escape }}
19 |
20 | {% endfor %}
21 | {% block pagination %}{% pagination cl user_agent %}{% endblock %}
22 |
23 |
24 | {% if cl.search_fields or cl.has_filters %}
25 |
26 | {% block search %}{% search_form cl user_agent %}{% endblock %}
27 | {% if cl.has_filters %}
28 | {% trans "Filter" %}
29 | {% for spec in cl.filter_specs %}
30 | {% admin_list_filter cl spec user_agent %}
31 | {% endfor %}
32 | {% endif %}
33 |
34 |
44 | {% endif %}
45 | {% endblock %}
46 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/base.html:
--------------------------------------------------------------------------------
1 | {% load i18n mobile_admin_media %}{% spaceless %}
2 |
3 |
4 | {% block title %}{% trans 'Django administration' %}{% endblock %}
5 |
6 |
7 | {% block blockbots %} {% endblock %}
8 |
9 |
10 | {% block extrahead %}{% endblock %}
11 | {% block extrastyle %}{% endblock %}
12 |
13 |
14 |
15 |
32 | {% block breadcrumbs %}{% endblock %}
33 | {% block content %}{{ content }}{% endblock %}
34 |
35 |
36 | {% endspaceless %}
37 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/registration/password_change_form.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n %}
3 | {% block breadcrumbs %}{% trans 'Home' %} {% trans 'Password change' %} {% endblock %}
4 | {% block tools %}
5 | {% if user.is_authenticated and user.is_staff %}
6 |
10 |
11 | {% trans 'Welcome,' %} {% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}
12 |
13 | {% endif %}
14 | {% endblock tools %}
15 |
16 | {% block header_logo %}
17 |
18 | {% endblock %}
19 |
20 | {% block content %}
21 |
44 | {% endblock %}
45 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/index.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n mobile_admin_modify %}
3 |
4 | {% block breadcrumbs %}
5 | {% trans "Site administration" %}
6 | {% trans 'Recent Actions' %}
7 | {% endblock %}
8 |
9 | {% block content %}
10 | {% if app_list %}
11 |
17 | {% else %}
18 |
19 |
{% trans "You don't have permission to edit anything." %}
20 |
21 | {% endif %}
22 |
23 |
40 |
50 | {% endblock %}
51 |
--------------------------------------------------------------------------------
/mobileadmin/sites.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin import sites
2 | from django.views.decorators.cache import never_cache
3 | from django.contrib.auth.views import password_change, password_change_done, logout
4 |
5 | from mobileadmin.decorators import mobile_templates
6 |
7 | class MobileAdminSite(sites.AdminSite):
8 | """
9 | A custom admin site to override the used templates.
10 | Add that to your urls.py:
11 |
12 | import mobileadmin
13 | urlpatterns += patterns('',
14 | (r'^m/(.*)', mobileadmin.site.root),
15 | )
16 | """
17 | logout_template = None
18 | password_change_template = None
19 | password_change_done_template = None
20 |
21 | def index(self, request, extra_context=None):
22 | return super(MobileAdminSite, self).index(request, extra_context)
23 | index = mobile_templates(index)
24 |
25 | def display_login_form(self, request, error_message='', extra_context=None):
26 | return super(MobileAdminSite, self).display_login_form(request, error_message, extra_context)
27 | display_login_form = mobile_templates(display_login_form)
28 |
29 | def app_index(self, request, app_label, extra_context=None):
30 | return super(MobileAdminSite, self).app_index(request, app_label, extra_context)
31 | app_index = mobile_templates(app_index)
32 |
33 | def logout(self, request):
34 | return logout(request, template_name=self.logout_template or 'registration/logged_out.html')
35 | logout = never_cache(mobile_templates(logout))
36 |
37 | def password_change(self, request):
38 | return password_change(request,
39 | template_name=self.password_change_template or 'registration/password_change_form.html',
40 | post_change_redirect='%spassword_change/done/' % self.root_path)
41 | password_change = mobile_templates(password_change)
42 |
43 | def password_change_done(self, request):
44 | return password_change_done(request,
45 | template_name=self.password_change_done_template or 'registration/password_change_done.html')
46 | password_change_done = mobile_templates(password_change_done)
47 |
48 | site = MobileAdminSite()
49 |
--------------------------------------------------------------------------------
/mobileadmin/templatetags/mobile_admin_modify.py:
--------------------------------------------------------------------------------
1 | import re
2 | from django import template
3 | from django.template.loader import render_to_string
4 | register = template.Library()
5 |
6 | admin_re = re.compile(r'^admin\/')
7 |
8 | def prepopulated_fields_js(context):
9 | """
10 | Creates a list of prepopulated_fields that should render Javascript for
11 | the prepopulated fields for both the admin form and inlines.
12 | """
13 | prepopulated_fields = []
14 | if context['add'] and 'adminform' in context:
15 | prepopulated_fields.extend(context['adminform'].prepopulated_fields)
16 | if 'inline_admin_formsets' in context:
17 | for inline_admin_formset in context['inline_admin_formsets']:
18 | for inline_admin_form in inline_admin_formset:
19 | if inline_admin_form.original is None:
20 | prepopulated_fields.extend(inline_admin_form.prepopulated_fields)
21 | context.update({'prepopulated_fields': prepopulated_fields})
22 | return context
23 | prepopulated_fields_js = register.inclusion_tag('admin/prepopulated_fields_js.html', takes_context=True)(prepopulated_fields_js)
24 |
25 | def mobile_inline_admin_formset(inline_admin_formset, user_agent):
26 | template_name = inline_admin_formset.opts.template
27 | if admin_re.match(template_name):
28 | # remove admin/ prefix to have a clean template name
29 | template_name = admin_re.sub('', template_name)
30 | return render_to_string((
31 | 'mobileadmin/%s/%s' % (user_agent, template_name),
32 | 'mobileadmin/%s' % template_name,
33 | 'admin/%s' % template_name,
34 | ), {
35 | 'inline_admin_formset': inline_admin_formset,
36 | 'user_agent': user_agent,
37 | })
38 | register.simple_tag(mobile_inline_admin_formset)
39 |
40 | def mobile_inline_admin_fieldset(fieldset, user_agent):
41 | return render_to_string((
42 | 'mobileadmin/%s/includes/fieldset.html' % user_agent,
43 | 'mobileadmin/includes/fieldset.html',
44 | 'admin/includes/fieldset.html',
45 | ), {
46 | 'fieldset': fieldset,
47 | })
48 | register.simple_tag(mobile_inline_admin_fieldset)
49 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | from distutils.core import setup
3 | from distutils.command.install import INSTALL_SCHEMES
4 |
5 | app_name = "mobileadmin"
6 | version = "0.5.2"
7 |
8 | # Tell distutils to put the data_files in platform-specific installation
9 | # locations. See here for an explanation:
10 | # http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb
11 | for scheme in INSTALL_SCHEMES.values():
12 | scheme['data'] = scheme['purelib']
13 |
14 | # Compile the list of packages available, because distutils doesn't have
15 | # an easy way to do this.
16 | packages, data_files = [], []
17 | root_dir = os.path.dirname(__file__)
18 | if root_dir:
19 | os.chdir(root_dir)
20 |
21 | for dirpath, dirnames, filenames in os.walk(app_name):
22 | # Ignore dirnames that start with '.'
23 | for i, dirname in enumerate(dirnames):
24 | if dirname.startswith('.'): del dirnames[i]
25 | if '__init__.py' in filenames:
26 | pkg = dirpath.replace(os.path.sep, '.')
27 | if os.path.altsep:
28 | pkg = pkg.replace(os.path.altsep, '.')
29 | packages.append(pkg)
30 | elif filenames:
31 | prefix = dirpath[len(app_name)+1:] # Strip "app_name/" or "app_name\"
32 | for f in filenames:
33 | data_files.append(os.path.join(prefix, f))
34 |
35 | setup(name='django-'+app_name,
36 | version=version,
37 | description='The Django admin interface for mobile devices.',
38 | long_description=open('docs/overview.rst').read(),
39 | author='Jannis Leidel',
40 | author_email='jannis@leidel.info',
41 | url='http://code.google.com/p/django-%s/' % app_name,
42 | package_dir={app_name: app_name},
43 | packages=packages,
44 | package_data={app_name: data_files},
45 | classifiers=['Development Status :: 4 - Beta',
46 | 'Environment :: Web Environment',
47 | 'Intended Audience :: Developers',
48 | 'License :: OSI Approved :: BSD License',
49 | 'Operating System :: OS Independent',
50 | 'Programming Language :: Python',
51 | 'Topic :: Utilities'],
52 | zip_safe=False,
53 | )
54 |
--------------------------------------------------------------------------------
/mobileadmin/decorators.py:
--------------------------------------------------------------------------------
1 | from mobileadmin.conf import settings
2 | from mobileadmin.utils import get_user_agent
3 |
4 | try:
5 | from functools import wraps
6 | except ImportError:
7 | from django.utils.functional import wraps # Python 2.3, 2.4 fallback.
8 |
9 | def mobile_templates(function):
10 | """
11 | Decorator to be used on ``AdminSite`` or ``ModelAdmin`` methods that
12 | changes the template of that method according to the current user agent
13 | by using a template mapping.
14 | """
15 | func_name = function.__name__
16 |
17 | def _change_templates(self, request, *args, **kwargs):
18 | if func_name in settings.TEMPLATE_MAPPING:
19 | path_list = []
20 | attr_name, template_name = settings.TEMPLATE_MAPPING[func_name]
21 | user_agent = get_user_agent(request)
22 | params = dict(template_name=template_name)
23 | if user_agent:
24 | params.update(user_agent=user_agent)
25 | path_list += [
26 | 'mobileadmin/%(user_agent)s/%(template_name)s',
27 | 'mobileadmin/%(template_name)s',
28 | ]
29 | # if self is a ModelAdmin instance add more of the default
30 | # templates as fallback
31 | if getattr(self, 'model', False):
32 | opts = self.model._meta
33 | params.update(dict(app_label=opts.app_label,
34 | object_name=opts.object_name.lower()))
35 | path_list = [
36 | 'mobileadmin/%(user_agent)s/%(app_label)s/%(object_name)s/%(template_name)s',
37 | 'mobileadmin/%(user_agent)s/%(app_label)s/%(template_name)s',
38 | ] + path_list + [
39 | 'admin/%(app_label)s/%(object_name)s/%(template_name)s',
40 | 'admin/%(app_label)s/%(template_name)s',
41 | ]
42 | path_list += [
43 | 'admin/%(template_name)s',
44 | '%(template_name)s',
45 | ]
46 | else:
47 | path_list += [
48 | 'admin/%(template_name)s',
49 | '%(template_name)s',
50 | ]
51 | setattr(self, attr_name, [path % params for path in path_list])
52 | return function(self, request, *args, **kwargs)
53 | return wraps(function)(_change_templates)
54 |
--------------------------------------------------------------------------------
/mobileadmin/templates/mobileadmin/mobile_safari/change_form.html:
--------------------------------------------------------------------------------
1 | {% extends "mobileadmin/mobile_safari/base.html" %}
2 | {% load i18n mobile_admin_modify mobile_admin_media %}
3 |
4 | {% block extrahead %}{{ block.super }}
5 |
6 |
7 | {% endblock %}
8 |
9 |
10 | {% block breadcrumbs %}{{ opts.verbose_name_plural|capfirst|escape }}
11 |
12 | {% if change %}
13 | {% if has_absolute_url %}{% trans "Go" %} {% endif%}
14 | {% trans "History" %}
15 | {% endif%}
16 |
17 | {% if add %}
18 | {% trans "Add" %}
19 | {% endif %}
20 | {% endblock %}
21 |
22 | {% block content %}
23 |
63 | {% endblock %}
64 |
--------------------------------------------------------------------------------
/mobileadmin/media/js/mobile_safari.js:
--------------------------------------------------------------------------------
1 | addEventListener("load", function() {
2 | setTimeout(updateLayout, 0);
3 | }, false);
4 |
5 | truncateRegistry = {};
6 | function truncate(elements) {
7 | for (ele in elements) {
8 | if (!(ele in truncateRegistry)) {
9 | truncateRegistry[ele] = {
10 | 'size': elements[ele],
11 | 'content': $(ele).innerHTML,
12 | };
13 | }
14 | }
15 | }
16 | function truncateDot(mode) {
17 | for (name in truncateRegistry) {
18 | var element = $(name);
19 | var ele = truncateRegistry[name];
20 | if (mode == 'on') {
21 | if (ele['content'].length > ele['size']) {
22 | if (ele['size'] > 3) {
23 | element.innerHTML = ele['content'].substring(0,(ele['size']-3)) + '...';
24 | } else {
25 | element.innerHTML = ele['content'].substring(0,ele['size']);
26 | }
27 | }
28 | } else {
29 | element.innerHTML = ele['content'];
30 | }
31 | }
32 | }
33 |
34 | var currentWidth = 0;
35 | function updateLayout() {
36 | switch(window.orientation) {
37 | case 0:
38 | case 180:
39 | var orient = "portrait";
40 | truncateDot('on');
41 | break;
42 |
43 | case 90:
44 | case -90:
45 | var orient = "landscape";
46 | truncateDot('off');
47 | break;
48 | }
49 | document.body.setAttribute("orient", orient);
50 | setTimeout(function() {
51 | window.scrollTo(0, 1);
52 | }, 100);
53 | }
54 |
55 | function $(ele) {
56 | return document.getElementById(ele);
57 | }
58 | function hasClass(ele,cls) {
59 | return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
60 | }
61 | function addClass(ele,cls) {
62 | if (!this.hasClass(ele,cls)) ele.className += " "+cls;
63 | }
64 | function removeClass(ele,cls) {
65 | if (hasClass(ele,cls)) {
66 | var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
67 | ele.className=ele.className.replace(reg,' ');
68 | }
69 | }
70 | function addAndRemoveClass(ele,_new,_old) {
71 | addClass(ele,_new);
72 | removeClass(ele,_old);
73 | }
74 | function switch_tabs(tabLabels) {
75 | function toggle() {
76 | var toActivate = this.id.substr(1);
77 | var toDeactivate = tabLabels[toActivate];
78 | $(toDeactivate).removeAttribute('selected');
79 | $(toActivate).setAttribute('selected', 'true');
80 | addAndRemoveClass($(this.id), 'active', 'inactive');
81 | addAndRemoveClass($("_"+toDeactivate), 'inactive', 'active');
82 | }
83 | for (label in tabLabels) {
84 | $("_"+label).addEventListener("click", toggle, false);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/mobileadmin/templatetags/mobile_admin_list.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.template.loader import render_to_string
3 | from django.contrib.admin.views.main import ALL_VAR, PAGE_VAR, SEARCH_VAR
4 |
5 | register = template.Library()
6 |
7 | def paginator_number(cl, i):
8 | if i == cl.page_num:
9 | classname = "active"
10 | else:
11 | classname = "inactive"
12 | return u'%d ' % (cl.get_query_string({PAGE_VAR: i}), classname, i+1)
13 | paginator_number = register.simple_tag(paginator_number)
14 |
15 | def pagination(cl, user_agent):
16 | paginator, page_num = cl.paginator, cl.page_num
17 |
18 | pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page
19 | if not pagination_required:
20 | page_range = []
21 | else:
22 | ON_EACH_SIDE = 1
23 |
24 | # If there are 4 or fewer pages, display links to every page.
25 | # Otherwise, do some fancy
26 | if paginator.num_pages <= 3:
27 | page_range = range(paginator.num_pages)
28 | else:
29 | # Insert "smart" pagination links, so that there are always ON_ENDS
30 | # links at either end of the list of pages, and there are always
31 | # ON_EACH_SIDE links at either end of the "current page" link.
32 | page_range = []
33 | if page_num > ON_EACH_SIDE:
34 | page_range.extend(range(0, ON_EACH_SIDE - 1))
35 | page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
36 | else:
37 | page_range.extend(range(0, page_num + 1))
38 | if page_num < (paginator.num_pages - ON_EACH_SIDE - 1):
39 | page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1))
40 | page_range.extend(range(paginator.num_pages, paginator.num_pages))
41 | else:
42 | page_range.extend(range(page_num + 1, paginator.num_pages))
43 |
44 | need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page
45 | return render_to_string((
46 | 'mobileadmin/%s/pagination.html' % user_agent,
47 | 'mobileadmin/pagination.html',
48 | 'admin/pagination.html'
49 | ), {
50 | 'cl': cl,
51 | 'pagination_required': pagination_required,
52 | 'show_all_url': need_show_all_link and cl.get_query_string({ALL_VAR: ''}),
53 | 'page_range': page_range,
54 | 'ALL_VAR': ALL_VAR,
55 | '1': 1,
56 | })
57 | register.simple_tag(pagination)
58 |
59 | def search_form(cl, user_agent):
60 | return render_to_string((
61 | 'mobileadmin/%s/search_form.html' % user_agent,
62 | 'mobileadmin/search_form.html',
63 | 'admin/search_form.html'
64 | ), {
65 | 'cl': cl,
66 | 'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field,
67 | 'search_var': SEARCH_VAR
68 | })
69 | register.simple_tag(search_form)
70 |
71 | def admin_list_filter(cl, spec, user_agent):
72 | return render_to_string((
73 | 'mobileadmin/%s/filter.html' % user_agent,
74 | 'mobileadmin/filter.html',
75 | 'admin/filter.html'
76 | ), {
77 | 'title': spec.title(),
78 | 'choices': list(spec.choices(cl)),
79 | })
80 | register.simple_tag(admin_list_filter)
81 |
--------------------------------------------------------------------------------
/mobileadmin/views.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.http import HttpResponseRedirect, HttpResponseNotFound, HttpResponseServerError
3 | from django.views import defaults
4 | from django.shortcuts import render_to_response
5 | from django.core.exceptions import PermissionDenied
6 | from django.template import Context, RequestContext, loader
7 | from django.utils.translation import ugettext, ugettext_lazy as _
8 |
9 | from mobileadmin import utils
10 |
11 | def auth_add_view(self, request):
12 | if not self.has_change_permission(request):
13 | raise PermissionDenied
14 | template_list = ['admin/auth/user/add_form.html']
15 | user_agent = utils.get_user_agent(request)
16 | if user_agent:
17 | template_list = [
18 | 'mobileadmin/%s/auth/user/add_form.html' % user_agent,
19 | ] + template_list
20 | if request.method == 'POST':
21 | form = self.add_form(request.POST)
22 | if form.is_valid():
23 | new_user = form.save()
24 | msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
25 | self.log_addition(request, new_user)
26 | if "_addanother" in request.POST:
27 | request.user.message_set.create(message=msg)
28 | return HttpResponseRedirect(request.path)
29 | elif '_popup' in request.REQUEST:
30 | return self.response_add(request, new_user)
31 | else:
32 | request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below."))
33 | return HttpResponseRedirect('../%s/' % new_user.id)
34 | else:
35 | form = self.add_form()
36 | return render_to_response(template_list, {
37 | 'title': _('Add user'),
38 | 'form': form,
39 | 'is_popup': '_popup' in request.REQUEST,
40 | 'add': True,
41 | 'change': False,
42 | 'has_add_permission': True,
43 | 'has_delete_permission': False,
44 | 'has_change_permission': True,
45 | 'has_file_field': False,
46 | 'has_absolute_url': False,
47 | 'auto_populated_fields': (),
48 | 'opts': self.model._meta,
49 | 'save_as': False,
50 | 'username_help_text': self.model._meta.get_field('username').help_text,
51 | 'root_path': self.admin_site.root_path,
52 | 'app_label': self.model._meta.app_label,
53 | }, context_instance=template.RequestContext(request))
54 |
55 | def page_not_found(request, template_name='404.html'):
56 | """
57 | Mobile 404 handler.
58 |
59 | Templates: `404.html`
60 | Context:
61 | request_path
62 | The path of the requested URL (e.g., '/app/pages/bad_page/')
63 | """
64 | user_agent = utils.get_user_agent(request)
65 | if user_agent:
66 | template_list = (
67 | 'mobileadmin/%s/404.html' % user_agent,
68 | template_name,
69 | )
70 | return HttpResponseNotFound(loader.render_to_string(template_list, {
71 | 'request_path': request.path,
72 | }, context_instance=RequestContext(request)))
73 | return defaults.page_not_found(request, template_name)
74 |
75 | def server_error(request, template_name='500.html'):
76 | """
77 | Mobile 500 error handler.
78 |
79 | Templates: `500.html`
80 | Context: None
81 | """
82 | user_agent = utils.get_user_agent(request)
83 | if user_agent:
84 | template_list = (
85 | 'mobileadmin/%s/500.html' % user_agent,
86 | template_name,
87 | )
88 | return HttpResponseServerError(loader.render_to_string(template_list))
89 | return defaults.server_error(request, template_name)
90 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | django-mobileadmin
2 | ==================
3 |
4 | Copyright (c) 2007, Jannis Leidel
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are
9 | met:
10 |
11 | * Redistributions of source code must retain the above copyright
12 | notice, this list of conditions and the following disclaimer.
13 | * Redistributions in binary form must reproduce the above
14 | copyright notice, this list of conditions and the following
15 | disclaimer in the documentation and/or other materials provided
16 | with the distribution.
17 | * Neither the name of the author nor the names of other
18 | contributors may be used to endorse or promote products derived
19 | from this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 |
33 | iUI
34 | ===
35 |
36 | Copyright (c) 2007, iUI Project Members
37 |
38 | All rights reserved.
39 |
40 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are
41 | met:
42 |
43 | * Redistributions of source code must retain the above copyright
44 | notice, this list of conditions and the following disclaimer.
45 | * Redistributions in binary form must reproduce the above
46 | copyright notice, this list of conditions and the following
47 | disclaimer in the documentation and/or other materials provided
48 | with the distribution.
49 | * Neither the name of the iUI Project nor the names of its
50 | contributors may be used to endorse or promote products derived
51 | from this software without specific prior written permission.
52 |
53 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
54 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
55 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
56 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
57 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
58 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
59 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
60 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
61 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
62 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
63 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
64 |
65 | Django
66 | ======
67 |
68 | Copyright (c) 2005, the Lawrence Journal-World
69 | All rights reserved.
70 |
71 | Redistribution and use in source and binary forms, with or without modification,
72 | are permitted provided that the following conditions are met:
73 |
74 | 1. Redistributions of source code must retain the above copyright notice,
75 | this list of conditions and the following disclaimer.
76 |
77 | 2. Redistributions in binary form must reproduce the above copyright
78 | notice, this list of conditions and the following disclaimer in the
79 | documentation and/or other materials provided with the distribution.
80 |
81 | 3. Neither the name of Django nor the names of its contributors may be used
82 | to endorse or promote products derived from this software without
83 | specific prior written permission.
84 |
85 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
86 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
87 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
88 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
89 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
90 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
91 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
92 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
93 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
94 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/mobileadmin/media/js/urlify.js:
--------------------------------------------------------------------------------
1 | var LATIN_MAP = {
2 | 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç':
3 | 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I',
4 | 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö':
5 | 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U',
6 | 'Ý': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 'a', 'ä':
7 | 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e',
8 | 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó':
9 | 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u',
10 | 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y'
11 | }
12 | var LATIN_SYMBOLS_MAP = {
13 | '©':'(c)'
14 | }
15 | var GREEK_MAP = {
16 | 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8',
17 | 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p',
18 | 'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w',
19 | 'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s',
20 | 'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i',
21 | 'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8',
22 | 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P',
23 | 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W',
24 | 'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I',
25 | 'Ϋ':'Y'
26 | }
27 | var TURKISH_MAP = {
28 | 'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U',
29 | 'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G'
30 | }
31 | var RUSSIAN_MAP = {
32 | 'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh',
33 | 'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o',
34 | 'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c',
35 | 'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu',
36 | 'я':'ya',
37 | 'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh',
38 | 'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O',
39 | 'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C',
40 | 'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu',
41 | 'Я':'Ya'
42 | }
43 | var UKRAINIAN_MAP = {
44 | 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g'
45 | }
46 | var CZECH_MAP = {
47 | 'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u',
48 | 'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T',
49 | 'Ů':'U', 'Ž':'Z'
50 | }
51 |
52 | var POLISH_MAP = {
53 | 'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z',
54 | 'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'e', 'Ł':'L', 'Ń':'N', 'Ó':'o', 'Ś':'S',
55 | 'Ź':'Z', 'Ż':'Z'
56 | }
57 |
58 | var LATVIAN_MAP = {
59 | 'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n',
60 | 'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'i',
61 | 'Ķ':'k', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'u', 'Ž':'Z'
62 | }
63 |
64 | var ALL_DOWNCODE_MAPS=new Array()
65 | ALL_DOWNCODE_MAPS[0]=LATIN_MAP
66 | ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP
67 | ALL_DOWNCODE_MAPS[2]=GREEK_MAP
68 | ALL_DOWNCODE_MAPS[3]=TURKISH_MAP
69 | ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP
70 | ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP
71 | ALL_DOWNCODE_MAPS[6]=CZECH_MAP
72 | ALL_DOWNCODE_MAPS[7]=POLISH_MAP
73 | ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP
74 |
75 | var Downcoder = new Object();
76 | Downcoder.Initialize = function()
77 | {
78 | if (Downcoder.map) // already made
79 | return ;
80 | Downcoder.map ={}
81 | Downcoder.chars = '' ;
82 | for(var i in ALL_DOWNCODE_MAPS)
83 | {
84 | var lookup = ALL_DOWNCODE_MAPS[i]
85 | for (var c in lookup)
86 | {
87 | Downcoder.map[c] = lookup[c] ;
88 | Downcoder.chars += c ;
89 | }
90 | }
91 | Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ;
92 | }
93 |
94 | downcode= function( slug )
95 | {
96 | Downcoder.Initialize() ;
97 | var downcoded =""
98 | var pieces = slug.match(Downcoder.regex);
99 | if(pieces)
100 | {
101 | for (var i = 0 ; i < pieces.length ; i++)
102 | {
103 | if (pieces[i].length == 1)
104 | {
105 | var mapped = Downcoder.map[pieces[i]] ;
106 | if (mapped != null)
107 | {
108 | downcoded+=mapped;
109 | continue ;
110 | }
111 | }
112 | downcoded+=pieces[i];
113 | }
114 | }
115 | else
116 | {
117 | downcoded = slug;
118 | }
119 | return downcoded;
120 | }
121 |
122 |
123 | function URLify(s, num_chars) {
124 | // changes, e.g., "Petty theft" to "petty_theft"
125 | // remove all these words from the string before urlifying
126 | s = downcode(s);
127 | removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from",
128 | "is", "in", "into", "like", "of", "off", "on", "onto", "per",
129 | "since", "than", "the", "this", "that", "to", "up", "via",
130 | "with"];
131 | r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi');
132 | s = s.replace(r, '');
133 | // if downcode doesn't hit, the char will be stripped here
134 | s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars
135 | s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
136 | s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
137 | s = s.toLowerCase(); // convert to lowercase
138 | return s.substring(0, num_chars);// trim to first num_chars chars
139 | }
140 |
141 |
--------------------------------------------------------------------------------
/docs/overview.rst:
--------------------------------------------------------------------------------
1 | =============================================
2 | The Django admin interface for mobile devices
3 | =============================================
4 |
5 | .. contents::
6 | :backlinks: none
7 |
8 | This is an alternative admin interface for Django for use with mobile devices
9 | such as the iPhone/iPod touch or Blackberry. Some would call it a theme or a
10 | skin, but actually it's more than that.
11 |
12 | It resembles almost all features of the regular Django admin interface and
13 | brings everything you need to add support for arbitrary devices.
14 |
15 | I hope you like it.
16 |
17 | Download & Installation
18 | =======================
19 |
20 | Get the source from the application site at::
21 |
22 | http://code.google.com/p/django-mobileadmin/
23 |
24 | To install the *mobileadmin*, follow these steps:
25 |
26 | 1. Follow the instructions in the INSTALL.txt file
27 | 2. Add ``'mobileadmin'`` to the INSTALLED_APPS_ setting of your Django site.
28 | That might look like this::
29 |
30 | INSTALLED_APPS = (
31 | # ...
32 | 'mobileadmin',
33 | )
34 |
35 | 3. Make sure you've installed_ the admin contrib app.
36 | 4. Add ``'mobileadmin.context_processors.user_agent'`` to your
37 | TEMPLATE_CONTEXT_PROCESSORS_ setting. It should look like this::
38 |
39 | TEMPLATE_CONTEXT_PROCESSORS = (
40 | 'django.core.context_processors.auth',
41 | 'django.core.context_processors.debug',
42 | 'django.core.context_processors.i18n',
43 | 'django.core.context_processors.media',
44 | 'django.core.context_processors.request',
45 | 'mobileadmin.context_processors.user_agent',
46 | )
47 |
48 | Usage
49 | =====
50 |
51 | Since *mobileadmin* follows the ideas of Django's admin interface you can
52 | easily reuse your existing admin setup:
53 |
54 | You use the default or custom ModelAdmin classes for each model you wanted
55 | to be editable in the admin interface and hooked up Django's default
56 | AdminSite instance with your URLconf.
57 |
58 | If that's the case you are able to re-use those ModelAdmin (sub-)classes
59 | by using *mobileadmin*'s separate admin site instance and its autoregister()
60 | function.
61 |
62 | 1. In your root URLconf -- just **below** the line where Django's
63 | admin.autodiscover() gets called -- import ``mobileadmin`` and call the
64 | function ``mobileadmin.autoregister()``::
65 |
66 | # urls.py
67 | from django.conf.urls.defaults import *
68 | from django.contrib import admin
69 |
70 | admin.autodiscover()
71 |
72 | import mobileadmin
73 | mobileadmin.autoregister()
74 |
75 | urlpatterns = patterns('',
76 | ('^admin/(.*)', admin.site.root),
77 | )
78 |
79 | This registers your existing admin configuration with *mobileadmin*.
80 |
81 | 2. Extend the urlpatterns to hook the default ``MobileAdminSite`` instance`
82 | with your favorite URL, e.g. ``/ma/``. Add::
83 |
84 | urlpatterns += patterns('',
85 | (r'^ma/(.*)', mobileadmin.sites.site.root),
86 | )
87 |
88 | *mobileadmin* is now replicating all of the regular admin features and
89 | uses templates adapted to the mobile device you are using.
90 |
91 | 3. Set the ``handler404`` and ``handler500`` variables in your URLConf to the
92 | views that are provided by *mobileadmin*::
93 |
94 | handler404 = 'mobileadmin.views.page_not_found'
95 | handler500 = 'mobileadmin.views.server_error'
96 |
97 | That is needed to load the ``404.html`` and ``500.html`` templates
98 | according to the user agent of the browser on your mobile device. It
99 | has an automatic fallback to `Django's default error handlers`_.
100 |
101 | .. _INSTALLED_APPS: http://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
102 | .. _ADMIN_MEDIA_PREFIX: http://docs.djangoproject.com/en/dev/ref/settings/#admin-media-prefix
103 | .. _TEMPLATE_CONTEXT_PROCESSORS: http://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
104 | .. _installed: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#overview
105 | .. _Django's default error handlers: http://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
106 |
107 | Extending ``mobileadmin``
108 | =========================
109 |
110 | *mobileadmin* comes with a default set of templates and patterns to
111 | recognize different devices and platforms:
112 |
113 | - Mobile Safari (iPhone/iPod touch)
114 |
115 | But it's made for being extended.
116 |
117 | Since the template loading depends on the user agent of the requesting client,
118 | *mobileadmin* tells Django to look for three templates, when trying to render
119 | one of the default admin views:
120 |
121 | 1. ``mobileadmin/USER_AGENT_NAME/VIEW_TEMPLATE.html``
122 | 2. ``mobileadmin/VIEW_TEMPLATE.html``
123 | 3. ``admin/index.html``
124 |
125 | ..where:
126 |
127 | - ``USER_AGENT`` is the short name of the user agent
128 | - ``VIEW_TEMPLATE`` is the name of the rendered template
129 |
130 | If you would try to access the login view with the iPhone for example, the
131 | following three templates would be tried to load:
132 |
133 | 1. ``mobileadmin/mobile_safari/login.html``
134 | 2. ``mobileadmin/login.html``
135 | 3. ``admin/index.html``
136 |
137 | ..where ``mobile_safari`` is the name of one of the default device patterns
138 | and ``login.html`` the name of the to needed template.
139 |
140 | Creating a custom ``mobileadmin`` setup
141 | ---------------------------------------
142 |
143 | You can add support for more user agents by adding ``MOBILEADMIN_USER_AGENTS``
144 | to your settings.py file -- consisting of a short name and a regualar
145 | expression, matching that user agent string::
146 |
147 | MOBILEADMIN_USER_AGENTS = {
148 | 'my_user_agent': r'.*MyUserAgent.*',
149 | }
150 |
151 | With that it would automatically check if the regular expression matches with
152 | the user agent of the current request and -- if yes -- try to load the
153 | templates ``mobileadmin/my_user_agent/login.html``, when accessing the the
154 | login page -- falling back to ``my_user_agent/login.html`` and later to
155 | ``admin/login.html``, if not found.
156 |
157 | Have a look at ``TEMPLATE_MAPPING`` in ``mobileadmin/conf/settings.py``
158 | if you want to know the default regular expressions.
159 |
160 | *mobileadmin* comes with a ``MobileAdminSite`` and a ``MobileModelAdmin``
161 | class that uses the default ``TEMPLATE_MAPPING`` and ``USER_AGENTS``
162 | settings out of the box::
163 |
164 | from mobileadmin import sites
165 |
166 | class MyMobileAdminSite(sites.MobileAdminSite):
167 | # define here whatever function you want
168 | pass
169 |
170 | But if you want to use the ability of *mobileadmin* to change the template
171 | depending on the user agent, you need to modify a bit of your admin classes.
172 |
173 | Luckily *mobileadmin* comes with a decorator to be used on ``AdminSite`` or
174 | ``ModelAdmin`` methods that changes the template of that method according to
175 | the current user agent by using a template mapping, which can be found in
176 | ``mobileadmin/conf/settings.py`` in the ``TEMPLATE_MAPPING`` variable.
177 |
178 | Those mappings are used by the decorator ``mobile_templates`` that applies
179 | them on the corresponding methods of your own ``AdminSite`` or
180 | ``ModelAdmin``, e.g.::
181 |
182 | from django.contrib.admin import sites
183 | from mobileadmin.decorators import mobile_templates
184 |
185 | class MyAdminSite(sites.AdminSite):
186 |
187 | def index(self, request, extra_context=None):
188 |
189 | # self.index_template is already automatically set here
190 | # do something cool here
191 |
192 | return super(MyAdminSite, self).index(request, extra_context)
193 | index = mobile_templates(index)
194 |
195 | Furthermore the default mappings can be extended in your site settings.py::
196 |
197 | MOBILEADMIN_TEMPLATE_MAPPING = {
198 | 'index': ('index_template', 'index.html'),
199 | }
200 |
201 | ..where:
202 |
203 | - ``index`` is the name of the function, whose class attribute and
204 | - ``index_template`` (an attribute of the method's class) would be set to the
205 | the file ``index.html``.
206 |
207 | Using custom mobile admin interfaces
208 | ------------------------------------
209 |
210 | In case you created your own mobile admin interface, you can use
211 | *mobileadmin*'s subclasses of Django's `ModelAdmin`_, `InlineModelAdmin`_
212 | and `AdminSite`_ classes, that include the neccesary bits to make it work.
213 |
214 | Just use it as you would use the base classes, e.g.::
215 |
216 | from mobileadmin import options
217 | from myproject.myapp.models import Author
218 |
219 | class MobileAuthorAdmin(options.MobileModelAdmin):
220 | pass
221 | mobileadmin.sites.site.register(Author, MobileAuthorAdmin)
222 |
223 | Then import ``mobileadmin`` in your URLconf to instantiate a
224 | ``MobileAdminSite`` object, use Django's ``autodiscover()`` to load
225 | ``INSTALLED_APPS`` admin.py modules and add an URL for the *mobileadmin* to
226 | the URLConf::
227 |
228 | # urls.py
229 | from django.conf.urls.defaults import *
230 | from django.contrib import admin
231 | import mobileadmin
232 |
233 | admin.autodiscover()
234 |
235 | urlpatterns = patterns('',
236 | ('^admin/(.*)', admin.site.root),
237 | (r'^ma/(.*)', mobileadmin.sites.site.root),
238 | )
239 |
240 | .. _InlineModelAdmin: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#inlinemodeladmin-objects
241 | .. _AdminSite: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#adminsite-objects
242 | .. _ModelAdmin: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects
243 |
244 | Media path
245 | ==========
246 |
247 | Please feel free to use some nice little helpers to find the path to
248 | *mobileadmin*'s media directory. If you are using Django (or any other Python
249 | software) to serve static files (which you shouldn't in production) just use
250 | for example::
251 |
252 | from mobileadmin.conf import settings
253 |
254 | mobileadmin_media_path = settings.MEDIA_PATH
255 | mobileadmin_media_prefix = settings.MEDIA_PREFIX
256 |
257 | You now have the full (platform-independent) path to the media directory of
258 | *mobileadmin* in the variable ``mobileadmin_media_path`` and the default URL
259 | prefix (``'/mobileadmin_media/'``) for the *mobileadmin* media -- CSS, Javascript
260 | and images -- in ``mobileadmin_media_prefix``. Just like the
261 | ADMIN_MEDIA_PREFIX_ but for the ``media`` directory of the *mobileadmin* app.
262 |
263 | You can of course optionally override the default URL prefix by setting
264 | a ``MOBILEADMIN_MEDIA_PREFIX`` in the settings.py file of your Django site.
265 | Please use a trailing slash. This makes updating *mobileadmin* much easier for
266 | you, since you now don't have to bother about different locations of the media
267 | directory.
268 |
269 | Serving *mobileadmin*'s static media
270 | ------------------------------------
271 |
272 | Even though using Django's ability to serve static files is strongly **NOT
273 | RECOMMENDED** for live production servers, it might be helpful to bring up
274 | *mobileadmin* for a test drive or an intranet website. Just add the following
275 | code to the URLConf (``urls.py``) of your Django site::
276 |
277 | from mobileadmin.conf import settings
278 |
279 | urlpatterns += patterns('django.views.static',
280 | (settings.MEDIA_REGEX, 'serve', {'document_root': settings.MEDIA_PATH}),
281 | )
282 |
283 | See how *mobileadmin*'s own settings module is loaded at the top of the snippet
284 | that enables you to obtain a ready-made regex for the static files
285 | (``MEDIA_REGEX``) and the platform-independent filesystem path to the media
286 | files (``MEDIA_PATH``).
287 |
288 | Support
289 | =======
290 |
291 | Please leave your `questions and problems`_ on the `designated Google Code site`_.
292 |
293 | .. _designated Google Code site: http://code.google.com/p/django-mobileadmin/
294 | .. _questions and problems: http://code.google.com/p/django-mobileadmin/issues/
295 |
--------------------------------------------------------------------------------
/mobileadmin/media/css/mobile_safari.css:
--------------------------------------------------------------------------------
1 | /* main.css (c) 2007-2008 Jannis Leidel, see LICENSE.txt for license */
2 |
3 | /* parts from iui.css (c) 2007 by iUI Project Members */
4 |
5 | body {
6 | margin: 0;
7 | font-family: Helvetica;
8 | background: #fff;
9 | color: #000;
10 | overflow-x: hidden;
11 | -webkit-user-select: none;
12 | -webkit-text-size-adjust: none;
13 | }
14 |
15 | .float-left {
16 | float: left;
17 | }
18 |
19 | .float-right {
20 | float: right;
21 | }
22 |
23 | .clear {
24 | clear: both;
25 | }
26 |
27 | body > *:not(.toolbar) {
28 | display: none;
29 | position: absolute;
30 | margin: 0;
31 | padding: 0;
32 | left: 0;
33 | top: 100px;
34 | width: 100%;
35 | min-height: 372px;
36 | }
37 |
38 | body[orient="landscape"] > *:not(.toolbar|.breadcrumb) {
39 | min-height: 268px;
40 | }
41 |
42 | body > *[selected="true"] {
43 | display: block;
44 | }
45 |
46 | body > .toolbar {
47 | box-sizing: border-box;
48 | -moz-box-sizing: border-box;
49 | -webkit-box-sizing: border-box;
50 | border-bottom: 1px solid #37657b;
51 | padding: 8px 7px 3px 2px;
52 | height: 60px;
53 | background: #417690 repeat-x;
54 | vertical-align: middle;
55 | }
56 |
57 | .toolbar > h1 {
58 | float: left;
59 | overflow: hidden;
60 | padding: 8px 4px 5px 2px;
61 | font-size: 20px;
62 | font-weight: bold;
63 | text-overflow: ellipsis;
64 | white-space: nowrap;
65 | }
66 |
67 | .toolbar > h1 a {
68 | color: #f4f379;
69 | text-decoration: none;
70 | padding: 5px 6px;
71 | }
72 |
73 | .toolbar > h1 a:active {
74 | background: none;
75 | background-color: none;
76 | }
77 |
78 | .toolbar div.greeting {
79 | color: #f4f379;
80 | font-size: 14px;
81 | font-weight: bold;
82 | padding: 1px 4px;
83 | line-height: 20px;
84 | }
85 |
86 | .toolbar > div.user-tools a,
87 | .toolbar > div.user-tools a:active,
88 | .toolbar > div.user-tools a:visited {
89 | line-height: 20px;
90 | padding: 1px 4px;
91 | font-size: 14px;
92 | font-weight: bold;
93 | text-decoration: none;
94 | color: #fff;
95 | text-align: right;
96 | float: right;
97 | }
98 |
99 | body > ul > li {
100 | position: relative;
101 | margin: 0;
102 | font-size: 20px;
103 | font-weight: bold;
104 | list-style: none;
105 | border-bottom: 1px solid #d9d9d9;
106 | padding: 10px 0 10px 10px;
107 | color: #444;
108 | }
109 |
110 | body > ul > li.group, ul#filter h3 {
111 | position: relative;
112 | font-weight: bold;
113 | color: #fff;
114 | background: url(../img/default-bg.gif) repeat-x;
115 | border-top: 1px solid #bdcddf;
116 | border-bottom: 1px solid #86a4c2;
117 | font-size: 16px;
118 | padding: 3px 10px 2px;
119 | margin-top: -1px;
120 | }
121 |
122 | body > ul > li.subgroup {
123 | background: url(../img/nav-bg.gif) -10px repeat-x;
124 | border-bottom: 1px solid #d9d9d9;
125 | color: #666;
126 | }
127 |
128 | body > ul > li.group:first-child {
129 | top: 0;
130 | border-top: none;
131 | margin-top: 0px;
132 | }
133 |
134 | body > ul > li > a {
135 | margin: -10px 0 -10px -10px;
136 | padding: 10px 32px 10px 10px;
137 | text-decoration: none;
138 | color: #5B80B2;
139 | font-size: 20px;
140 | display: block;
141 | background: url(../img/arrow.png) no-repeat right center;
142 | }
143 |
144 | body > ul > li > a.selected {
145 | color: #666;
146 | background: none;
147 | }
148 |
149 | ul#filter li.selected a, ul#filter li.selected a:link {
150 | color: #666;
151 | background: none;
152 | }
153 |
154 | body > ul > li.paginator {
155 | color: #666;
156 | background: #fff url(../img/nav-bg-reverse.gif) repeat-x top;
157 | min-height: 21px;
158 | padding: 9px 0;
159 | font-size: 18px;
160 | font-weight: bold;
161 | margin: 0px;
162 | }
163 |
164 | body > ul > li.paginator a:link,
165 | body > ul > li.paginator a:active,
166 | body > ul > li.paginator a:visited {
167 | text-decoration: none;
168 | color: #5B80B2;
169 | background: none;
170 | margin: -6px 8px 0px -10px;
171 | }
172 |
173 | body > ul > li.paginator a.inactive,
174 | body > ul > li.paginator a.active {
175 | background: none;
176 | }
177 |
178 | body > ul > li.paginator span a.inactive,
179 | body > ul > li.paginator span a.active {
180 | padding: 9px 12px;
181 | margin: -9px -1px 0px 0px;
182 | }
183 |
184 | body > ul > li.paginator span a.active {
185 | color: #666;
186 | margin-left: 0px;
187 | margin-right: 0px;
188 | padding-left: 13px;
189 | }
190 |
191 | body > ul > li.paginator a.inactive {
192 | border: 1px solid #ccc;
193 | border-top: none;
194 | color: #5B80B2;
195 | background: #fff url(../img/nav-bg.gif) repeat-x bottom;
196 | }
197 |
198 | body > ul > li.paginator span.float-right a.inactive {
199 | border-right: none;
200 | }
201 |
202 | body > ul > li.paginator span.active {
203 | padding-right: 12px;
204 | }
205 |
206 | body > ul > li.paginator span a.showall {
207 | margin: 0px;
208 | }
209 |
210 | body > .breadcrumbs {
211 | box-sizing: border-box;
212 | -moz-box-sizing: border-box;
213 | -webkit-box-sizing: border-box;
214 | position: absolute;
215 | background: url(../img/nav-bg-reverse.gif) repeat-x;
216 | color: #666;
217 | top: 60px;
218 | min-height: 40px;
219 | max-height: 40px;
220 | padding: 9px 0px 9px 8px;
221 | font-size: 18px;
222 | border-top: 1px solid #fff;
223 | border-bottom: 1px solid #cbcbcb;
224 | font-weight: bold;
225 | }
226 |
227 | .breadcrumbs a:link,
228 | .breadcrumbs a:active,
229 | .breadcrumbs a:visited {
230 | text-decoration: none;
231 | color: #5B80B2;
232 | background: none;
233 | padding: 9px 16px 9px 10px;
234 | margin: -9px 4px 0px -6px;
235 | }
236 |
237 | .breadcrumbs a:active {
238 | color: #036;
239 | }
240 |
241 | .breadcrumbs a.active,
242 | .breadcrumbs a.inactive {
243 | background: none;
244 | }
245 |
246 | .breadcrumbs span {
247 | padding-right: 12px;
248 | }
249 |
250 | .breadcrumbs a.active,
251 | .breadcrumbs span.active {
252 | color: #666;
253 | }
254 |
255 | .breadcrumbs a.inactive,
256 | .breadcrumbs span.inactive {
257 | padding-right: 12px;
258 | padding-left: 12px;
259 | padding-bottom: 8px;
260 | margin-right: 0px;
261 | margin-left: -1px;
262 | border: 1px solid #ccc;
263 | border-top: none;
264 | color: #5B80B2;
265 | background: #fff url(../img/nav-bg.gif) repeat-x bottom;
266 | }
267 |
268 | .breadcrumbs a.float-left {
269 | margin-left: -11px;
270 | }
271 |
272 | .breadcrumbs a.float-right {
273 | text-align: right;
274 | }
275 |
276 | .breadcrumbs a.active {
277 | margin-left: -8px;
278 | padding-right: 21px;
279 | }
280 |
281 | .breadcrumbs a#_recent.inactive {
282 | padding-left: 12px;
283 | }
284 |
285 | .breadcrumbs a#_recent.active,
286 | .breadcrumbs a#_filter.active {
287 | padding-left: 22px;
288 | margin-left: 0;
289 | padding-right: 9px;
290 | }
291 |
292 | .breadcrumbs a#_filter span.searchimg {
293 | background: url(../img/search.png) no-repeat center center;
294 | }
295 |
296 | .submit-row {
297 | box-sizing: border-box;
298 | -moz-box-sizing: border-box;
299 | -webkit-box-sizing: border-box;
300 | background: #fff url(../img/nav-bg-reverse.gif) repeat-x;
301 | color: #666;
302 | top: 48px;
303 | min-height: 40px;
304 | max-height: 40px;
305 | padding: 9px 0px 9px 8px;
306 | font-size: 18px;
307 | border: 1px solid #d9d9d9;
308 | border-width: 1px 0;
309 | font-weight: bold;
310 | }
311 |
312 | .submit-row a:link,
313 | .submit-row a:active,
314 | .submit-row a:visited {
315 | text-decoration: none;
316 | color: #5B80B2;
317 | padding: 6px;
318 | margin: -6px 6px 0px 6px;
319 | padding: 9px 16px 9px 10px;
320 | margin: -9px 4px 0px -6px;
321 | }
322 |
323 | .submit-row a:active {
324 | color: #036;
325 | }
326 |
327 | .submit-row a.inactive, .submit-row a.active {
328 | background: none;
329 | }
330 |
331 | .submit-row span {
332 | padding-right: 12px;
333 | }
334 |
335 | .submit-row a.active, .submit-row span.active {
336 | color: #666;
337 | }
338 |
339 | .submit-row a.inactive, .submit-row span.inactive {
340 | color: #5B80B2;
341 | padding-right: 12px;
342 | padding-left: 12px;
343 | padding-bottom: 8px;
344 | margin-right: 0px;
345 | margin-left: -1px;
346 | border: 1px solid #ccc;
347 | border-top: none;
348 | background: #fff url(../img/nav-bg.gif) repeat-x bottom;
349 | }
350 |
351 | .submit-row a.float-left {
352 | margin-left: -11px;
353 | }
354 |
355 | .submit-row a.submit {
356 | color: #fff;
357 | background: #BCD1E8 url(../img/default-bg.gif) repeat-x bottom;
358 |
359 |
360 | /* text-shadow: #BCD1E8 1px 1px 0;
361 | */
362 | }
363 |
364 | .submit-row a.delete {
365 | color: #CC3434;
366 | }
367 |
368 | /***/
369 |
370 | body > ul > li.searchbar {
371 | color: #666;
372 | background: #fff url(../img/nav-bg-reverse.gif) repeat-x top;
373 | min-height: 21px;
374 | padding: 9px 0;
375 | font-size: 18px;
376 | font-weight: bold;
377 | }
378 |
379 | body > ul > li.searchbar a:link,
380 | body > ul > li.searchbar a:active,
381 | body > ul > li.searchbar a:visited {
382 | text-decoration: none;
383 | color: #5B80B2;
384 | background: none;
385 | margin: -6px 8px 0px -10px;
386 | }
387 |
388 | body > ul > li.searchbar span a.inactive,
389 | body > ul > li.searchbar span a.active {
390 | padding: 9px 12px;
391 | margin: -9px 0px 0px;
392 | }
393 |
394 | body > ul > li.searchbar span.float-left a.inactive {
395 | margin-right: 4px;
396 | }
397 |
398 | body > ul > li.searchbar a.inactive {
399 | border: 1px solid #ccc;
400 | border-top: none;
401 | color: #5B80B2;
402 | background: #fff url(../img/nav-bg.gif) repeat-x bottom;
403 | }
404 |
405 | body > ul > li.searchbar span.float-right a.inactive {
406 | border-right: none;
407 | }
408 |
409 | body > ul > li.searchbar span.active {
410 | padding-right: 12px;
411 | }
412 |
413 | /***/
414 |
415 | input, textarea, select {
416 | box-sizing: border-box;
417 | -webkit-box-sizing: border-box;
418 | width: 100%;
419 | margin: 8px 0 0 0;
420 | font-size: 16px;
421 | font-weight: normal;
422 | background: none;
423 | }
424 |
425 | body > .panel {
426 | box-sizing: border-box;
427 | -webkit-box-sizing: border-box;
428 | background: #fff;
429 | }
430 |
431 | .panel > fieldset {
432 | position: relative;
433 | margin: 0 0 20px 0;
434 | padding: 0;
435 | background: #FFFFFF;
436 | border: none;
437 | text-align: right;
438 | font-size: 16px;
439 | }
440 |
441 | .form-row {
442 | position: relative;
443 | min-height: 42px;
444 | text-align: left;
445 | padding: 0 8px;
446 | }
447 |
448 | fieldset > .form-row:last-child {
449 | border-bottom: none !important;
450 | }
451 |
452 | fieldset > .form-row:first-child {
453 | border-top: none;
454 | }
455 |
456 | .form-row input,
457 | #search > input[type="text"] {
458 | display: block;
459 | box-sizing: border-box;
460 | -webkit-box-sizing: border-box;
461 | margin: 0;
462 | padding: 12px 10px 0 12px;
463 | height: 40px;
464 | background: none;
465 | -webkit-border-radius: 5px;
466 | border: 1px solid #D9D9D9;
467 | }
468 |
469 | #search > input[type="text"] {
470 | margin-right: 20px;
471 | }
472 |
473 | #search {
474 | margin: 5px 10px 5px 0px;
475 | background: none;
476 | }
477 |
478 | .form-row > input.vDateField,
479 | .form-row > input.vTimeField {
480 | width: 100%;
481 | }
482 |
483 | .form-row > input.vTimeField {
484 | margin-top: 10px;
485 | }
486 |
487 | .form-row > input[type="checkbox"] {
488 | width: 30px;
489 | height: 30px;
490 | margin: 8px 0px 0px;
491 | border: 0;
492 | -webkit-border-radius: 8px;
493 | background: #fff url(../img/default-bg.gif) repeat-x bottom;
494 | float: left;
495 | clear: left;
496 | }
497 |
498 | .radiolist input {
499 | position:relative;
500 | width: 30px;
501 | height: 30px;
502 | margin: -6px 0;
503 | padding: 0;
504 | border: 0;
505 | -webkit-border-radius: 8px;
506 | background: #fff url(../img/default-bg.gif) repeat-x bottom;
507 | float: left;
508 | clear: left;
509 | }
510 |
511 | .form-row span {
512 | float: right;
513 | margin-top: -20px;
514 | }
515 |
516 | .form-row > select {
517 | -webkit-border-radius: 5px;
518 | border: 1px solid #D9D9D9;
519 | margin: 10px 0 0 0;
520 | padding: 12px 10px 10 12px;
521 | height: 40px;
522 | clear: left;
523 | }
524 |
525 | .form-row > textarea {
526 | box-sizing: border-box;
527 | -webkit-box-sizing: border-box;
528 | display: block;
529 | margin: 0;
530 | border: none;
531 | padding: 12px 10px 0 12px;
532 | height: 126px;
533 | background: none;
534 | -webkit-border-radius: 5px;
535 | border: 1px solid #D9D9D9;
536 | }
537 |
538 | .form-row label,
539 | .form-row p,
540 | #search > label {
541 | position: relative;
542 | top: 9px;
543 | padding: 0 3px;
544 | margin-left: 10px;
545 | font-weight: normal;
546 | font-size: 16px;
547 | color: #666;
548 | text-align: left;
549 | background-color: #fff;
550 | }
551 |
552 | .form-row p.datetime {
553 | margin: 5px -13px 0 0px;
554 | background-color: #fff !important;
555 | padding: 5px 0 0 13px;
556 | }
557 |
558 | .form-row p.datetime input {
559 | margin: 5px 0px 10px -13px;
560 | }
561 |
562 | .form-row p.datetime br {
563 | display: none;
564 | }
565 |
566 | .form-row p.datetime span {
567 | display: none;
568 | }
569 |
570 | .form-row label.vCheckboxLabel {
571 | float: left;
572 | padding: 0.4em 0;
573 | }
574 |
575 | .form-row label.required {
576 | font-weight: bold;
577 | }
578 |
579 | .form-row > ul.radiolist li {
580 | list-style: none;
581 | margin: 10px 10px 20px;
582 | padding: 0;
583 | }
584 | .form-row > ul.radiolist label {
585 | top: 13px;
586 | }
587 |
588 | .form-row > ul.inline {
589 | margin-left: 13px;
590 | padding: 0;
591 | }
592 |
593 | .form-row > ul.inline li {
594 | padding: 2px;
595 | }
596 |
597 | .form-row > ul {
598 | color: #333;
599 | list-style: none;
600 | margin: 0px 2px;
601 | padding: 0px;
602 | font-weight: normal;
603 | }
604 |
605 | .form-row > ul > li > ul {
606 | padding: 10px 20px;
607 | }
608 |
609 | .form-row a.add-another {
610 | display: none;
611 | }
612 |
613 | .panel > h2 {
614 | margin: 0;
615 | padding: 2px 10px;
616 | font-size: 14px;
617 | font-weight: bold;
618 | color: #fff;
619 | background: url(../img/default-bg.gif) repeat-x;
620 | border-top: 1px solid #bdcddf;
621 | border-bottom: 1px solid #86a4c2;
622 | }
623 |
624 | .panel > h3 {
625 | margin: 0;
626 | padding: 2px 10px;
627 | font-size: 14px;
628 | font-weight: bold;
629 | color: #fff;
630 | background: url(../img/nav-bg.gif) -10px repeat-x;
631 | border-bottom: 1px solid #d9d9d9;
632 | border-top: 1px solid #d9d9d9;
633 | color: #666;
634 | }
635 |
636 | .panel > h1 {
637 | margin: 0;
638 | padding: 10px;
639 | font-size: 16px;
640 | font-weight: bold;
641 | color: #666;
642 | background: none;
643 | }
644 |
645 | .panel > h1.error {
646 | color: #CC2830;
647 | }
648 |
649 | .panel a.help-link {
650 | float: right;
651 | color: #ccc;
652 | text-decoration: none;
653 | font-weight: bold;
654 | padding: 2px 4px;
655 | font-size: 14px;
656 | }
657 |
658 | ul.errorlist, ul.errorlist li {
659 | list-style: none;
660 | color: #cc2830;
661 | font-size: 12px;
662 | padding: 2px 4px;
663 | float: right;
664 | font-weight: bold;
665 | }
666 |
667 | ul.errorlist > li {
668 | list-style: none;
669 | }
670 |
671 | #preloader {
672 | display: none;
673 | background-image: url(../img/default-bg.gif), url(../img/default-bg-reverse.gif),
674 | url(../img/icon_searchbox.png), url(../img/arrow.png),
675 | url(../img/nav-bg.gif), url(../img/nav-bg-reverse.gif);
676 | }
--------------------------------------------------------------------------------