port to new django, AI automated

This commit is contained in:
2026-03-30 22:35:36 +02:00
parent e2d166e437
commit 372da3caa9
215 changed files with 9283 additions and 2981 deletions

2
.gitignore vendored
View File

@@ -4,5 +4,3 @@
/.idea /.idea
/._bootstrapTemplates /._bootstrapTemplates
/env /env
/venv
/bower_components

View File

@@ -1,24 +0,0 @@
# docker build -t "blechreiz_website" .
FROM ubuntu:20.04
RUN apt-get update \
&& apt-get install -y python3 python3-venv python3-wheel \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
ADD . /blechreiz
ENV BLECHREIZ_DATA /data
ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH /blechreiz
RUN cd /blechreiz \
&& python3 -m venv env \
&& . /blechreiz/env/bin/activate \
&& rm -r /blechreiz/blechreiz/data \
&& /blechreiz/env/bin/pip install -r requirements.txt
EXPOSE 8000
CMD /blechreiz/env/bin/python3 /blechreiz/manage.py collectstatic --noinput \
&& /blechreiz/env/bin/gunicorn -b 0.0.0.0:8000 blechreiz.wsgi

View File

@@ -1 +0,0 @@
{"_module": "oauth2client.client", "token_expiry": "2018-12-18T12:55:20Z", "access_token": "ya29.Gl12BuJlFBtRZSBtMkxubhfG8rYDsLUgc1yKMpO9_aE3tmi4sLFGLq_Djq5R9zr1919oZAobX0oaD6bTj3s4i9kqWfw0gpOGKJKRHM1Y9gHc0JsCe2kChNszu_RCAaE", "token_uri": "https://accounts.google.com/o/oauth2/token", "invalid": false, "token_response": {"access_token": "ya29.Gl12BuJlFBtRZSBtMkxubhfG8rYDsLUgc1yKMpO9_aE3tmi4sLFGLq_Djq5R9zr1919oZAobX0oaD6bTj3s4i9kqWfw0gpOGKJKRHM1Y9gHc0JsCe2kChNszu_RCAaE", "scope": "https://www.googleapis.com/auth/calendar", "expires_in": 3600, "token_type": "Bearer"}, "client_id": "34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com", "id_token": null, "client_secret": "y4t9XBrJdCODPTO5UvtONWWn", "revoke_uri": "https://accounts.google.com/o/oauth2/revoke", "_class": "OAuth2Credentials", "refresh_token": "1/txixroRJyyVmuENPpaXyB_bGeXa1XV-pClAxqKHk_5-JW1qGFE0Gl-WlgCu1Eizq", "user_agent": null}

Binary file not shown.

View File

@@ -1,37 +1,41 @@
from django.http import HttpResponseRedirect
from django.conf import settings
import re import re
from django.conf import settings
from django.http import HttpResponseRedirect
class EnforceLoginMiddleware: class EnforceLoginMiddleware:
""" """
Middlware class which requires the user to be authenticated for all urls except Middleware class which requires the user to be authenticated for all urls except
those defined in PUBLIC_URLS in settings.py. PUBLIC_URLS should be a tuple of regular those defined in PUBLIC_URLS in settings.py. PUBLIC_URLS should be a tuple of regular
expresssions for the urls you want anonymous users to have access to. If PUBLIC_URLS expressions for the urls you want anonymous users to have access to. If PUBLIC_URLS
is not defined, it falls back to LOGIN_URL or failing that '/accounts/login/'. is not defined, it falls back to LOGIN_URL or failing that '/accounts/login/'.
Requests for urls not matching PUBLIC_URLS get redirected to LOGIN_URL with next set Requests for urls not matching PUBLIC_URLS get redirected to LOGIN_URL with next set
to original path of the unauthenticted request. to original path of the unauthenticated request.
Any urls statically served by django are excluded from this check. To enforce the same Any urls statically served by django are excluded from this check. To enforce the same
validation on these set SERVE_STATIC_TO_PUBLIC to False. validation on these set SERVE_STATIC_TO_PUBLIC to False.
""" """
def __init__(self, get_response): def __init__(self, get_response):
self.login_url = getattr(settings, 'LOGIN_URL', '/accounts/login/')
self.get_response = get_response self.get_response = get_response
self.login_url = getattr(settings, "LOGIN_URL", "/accounts/login/")
if hasattr(settings, 'PUBLIC_URLS'): if hasattr(settings, "PUBLIC_URLS"):
public_urls = [re.compile(url) for url in settings.PUBLIC_URLS] public_urls = [re.compile(url) for url in settings.PUBLIC_URLS]
else: else:
public_urls = [(re.compile("^%s/?$" % (self.login_url[1:])))] public_urls = [re.compile("^%s/?$" % (self.login_url[1:]))]
if getattr(settings, 'SERVE_STATIC_TO_PUBLIC', True):
root_urlconf = __import__(settings.ROOT_URLCONF)
public_urls.extend([re.compile(url.regex)
for url in root_urlconf.urls.urlpatterns
if url.__dict__.get('_callback_str') == 'django.views.static.serve'
])
self.public_urls = tuple(public_urls) self.public_urls = tuple(public_urls)
def __call__(self, request): def __call__(self, request):
# Check if user needs to be redirected to login
redirect_response = self.check_login(request)
if redirect_response:
return redirect_response
return self.get_response(request)
def check_login(self, request):
""" """
Redirect anonymous users to login_url from non public urls Redirect anonymous users to login_url from non public urls
""" """
@@ -40,52 +44,63 @@ class EnforceLoginMiddleware:
for url in self.public_urls: for url in self.public_urls:
if url.match(request.path[1:]): if url.match(request.path[1:]):
return None return None
return HttpResponseRedirect("%s?next=%s" % (self.login_url, request.path)) return HttpResponseRedirect(
"%s?next=%s" % (self.login_url, request.path)
)
except AttributeError: except AttributeError:
return HttpResponseRedirect("%s?next=%s" % (self.login_url, request.path)) return HttpResponseRedirect("%s?next=%s" % (self.login_url, request.path))
return None
return self.get_response(request)
class DetectDevice: class DetectDevice:
"""
Middleware to detect the device type from user agent string.
"""
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
def __call__(self, request): def __call__(self, request):
device = self.mobile(request) request.device = self.detect_device(request)
return self.get_response(request)
request.device = device def detect_device(self, request):
response = self.get_response(request)
return response
def mobile(self, request):
device = {} device = {}
ua = request.META.get('HTTP_USER_AGENT', '').lower() ua = request.META.get("HTTP_USER_AGENT", "").lower()
if ua.find("iphone") > 0: if "iphone" in ua:
device['iphone'] = "iphone" + re.search("iphone os (\d)", ua).groups(0)[0] match = re.search(r"iphone os (\d)", ua)
if match:
device["iphone"] = "iphone" + match.group(1)
else:
device["iphone"] = "iphone"
if ua.find("ipad") > 0: if "ipad" in ua:
device['ipad'] = "ipad" device["ipad"] = "ipad"
if ua.find("android") > 0: if "android" in ua:
device['android'] = "android" + re.search("android (\d\.\d)", ua).groups(0)[0].translate(None, '.') match = re.search(r"android (\d\.\d)", ua)
if match:
version = match.group(1).replace(".", "")
device["android"] = "android" + version
else:
device["android"] = "android"
if ua.find("blackberry") > 0: if "blackberry" in ua:
device['blackberry'] = "blackberry" device["blackberry"] = "blackberry"
if ua.find("windows phone os 7") > 0: if "windows phone os 7" in ua:
device['winphone7'] = "winphone7" device["winphone7"] = "winphone7"
if ua.find("iemobile") > 0: if "iemobile" in ua:
device['winmo'] = "winmo" device["winmo"] = "winmo"
if not device: # either desktop, or something we don't care about. if not device:
device['baseline'] = "baseline" # either desktop, or something we don't care about.
device["baseline"] = "baseline"
# spits out device names for CSS targeting, to be applied to <html> or <body>. # spits out device names for CSS targeting, to be applied to <html> or <body>.
device['classes'] = " ".join(v for (k, v) in device.items()) device["classes"] = " ".join(v for (k, v) in device.items())
return device return device

View File

@@ -1,233 +1,205 @@
# Setting the path # Setting the path
import os import os
from pathlib import Path
gettext = lambda s: s BASE_DIR = Path(__file__).resolve().parent
PROJECT_PATH = os.path.abspath(os.path.dirname(__file__)) PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))
DATA_PATH = os.environ.get('BLECHREIZ_DATA', default=os.path.join(PROJECT_PATH, "data"))
# Django settings for blechreiz project. # Django settings for blechreiz project.
DEBUG = True DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = ( ADMINS = [
('Martin Bauer', 'bauer_martin@gmx.de'), ("Martin Bauer", "bauer_martin@gmx.de"),
) ]
MANAGERS = ADMINS MANAGERS = ADMINS
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(DATA_PATH, 'database.sqlite'), "NAME": os.path.join(PROJECT_PATH, "database.sqlite"),
} }
} }
# Default primary key field type
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Email # Email
EMAIL_HOST = 'smtp.blechreiz.com' EMAIL_HOST = "smtp.blechreiz.com"
EMAIL_HOST_USER = 'm02b721a' EMAIL_HOST_USER = "m02b721a"
EMAIL_HOST_PASSWORD = '9Hp4WG5bZ2WVPX5z' EMAIL_HOST_PASSWORD = "9Hp4WG5bZ2WVPX5z"
EMAIL_USE_TLS = False EMAIL_USE_TLS = False
# Hosts/domain names that are valid for this site; required if DEBUG is False # Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts ALLOWED_HOSTS = ["localhost", "127.0.0.1", ".blechreiz.com", ".bauer.technology"]
ALLOWED_HOSTS = []
# Local time zone for this installation. Choices can be found here: # Local time zone for this installation.
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name TIME_ZONE = "Europe/Berlin"
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'Europe/Berlin'
# Language code for this installation. All choices can be found here: # Language code for this installation.
# http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = "de"
LANGUAGE_CODE = 'de'
SITE_ID = 1 SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not # Internationalization
# to load the internationalization machinery.
USE_I18N = True USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files. # Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/var/www/example.com/media/" MEDIA_ROOT = PROJECT_PATH + "/media/"
MEDIA_ROOT = PROJECT_PATH + "/media"
# URL that handles the media served from MEDIA_ROOT. Make sure to use a # URL that handles the media served from MEDIA_ROOT.
# trailing slash. MEDIA_URL = "/media/"
# Examples: "http://example.com/media/", "http://media.example.com/"
MEDIA_URL = '/media/'
# Absolute path to the directory static files should be collected to. # Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files STATIC_ROOT = PROJECT_PATH + "/static_collection"
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/var/www/example.com/static/"
STATIC_ROOT = PROJECT_PATH + '/static_collection'
# URL prefix for static files. # URL prefix for static files.
# Example: "http://example.com/static/", "http://static.example.com/" STATIC_URL = "/static/"
STATIC_URL = '/static/'
LOGIN_URL = "/musicians/login" LOGIN_URL = "/musicians/login"
PUBLIC_URLS = ("^musicians/login/?$", "^musicians/login/usernames/?$", "^eventplanner_gcal/gcalApiCallback*") PUBLIC_URLS = (
"^musicians/login/?$",
"^musicians/login/usernames/?$",
"^eventplanner_gcal/gcalApiCallback*",
)
# Additional locations of static files # Additional locations of static files
STATICFILES_DIRS = ( STATICFILES_DIRS = [
#PROJECT_PATH + '/static', PROJECT_PATH + "/static",
# Put strings here, like "/home/html/static" or "C:/www/django/static". ]
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
# List of finder classes that know how to find static files in # List of finder classes that know how to find static files in various locations.
# various locations. STATICFILES_FINDERS = [
STATICFILES_FINDERS = ( "django.contrib.staticfiles.finders.FileSystemFinder",
'django.contrib.staticfiles.finders.FileSystemFinder', "django.contrib.staticfiles.finders.AppDirectoriesFinder",
'django.contrib.staticfiles.finders.AppDirectoriesFinder', ]
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody. # Make this unique, and don't share it with anybody.
SECRET_KEY = 'x$8&9s6t%eeg=wyhar87934wh_s$dbpm(73g4ho&n)9_wogj6p' SECRET_KEY = "x$8&9s6t%eeg=wyhar87934wh_s$dbpm(73g4ho&n)9_wogj6p"
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"blechreiz.middleware.EnforceLoginMiddleware",
"blechreiz.middleware.DetectDevice",
]
ROOT_URLCONF = "blechreiz.urls"
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = "blechreiz.wsgi.application"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [ "DIRS": [
PROJECT_PATH + '/templates', PROJECT_PATH + "/templates",
], ],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
# Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this "django.template.context_processors.debug",
# list if you haven't customized them: "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.template.context_processors.i18n', "django.contrib.messages.context_processors.messages",
'django.template.context_processors.request', "django.template.context_processors.i18n",
'django.template.context_processors.media', "django.template.context_processors.media",
'django.template.context_processors.static', "django.template.context_processors.static",
'sekizai.context_processors.sekizai', "sekizai.context_processors.sekizai",
], ],
}, },
}, },
] ]
MIDDLEWARE = (
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
#'blechreiz.middleware.EnforceLoginMiddleware',
'blechreiz.middleware.DetectDevice',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'blechreiz.urls' INSTALLED_APPS = [
"django.contrib.admin",
# Python dotted path to the WSGI application used by Django's runserver. "django.contrib.auth",
WSGI_APPLICATION = 'blechreiz.wsgi.application' "django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
INSTALLED_APPS = ( "django.contrib.messages",
'django.contrib.auth', "django.contrib.staticfiles",
'django.contrib.contenttypes', # Third-party apps
'django.contrib.sessions', "crispy_forms",
'django.contrib.sites', "crispy_bootstrap5",
'django.contrib.messages', "sekizai",
'django.contrib.staticfiles', "rest_framework",
'django.contrib.admin', # Own apps
'django.contrib.admindocs', "bootstrapTheme",
'crispy_forms', # better looking forms ( bootstrap ) "website",
'sekizai', # for the addtoblock directive in templates "musicians",
'rest_framework', # for event management api "eventplanner",
"eventplanner_gcal",
# Own Things "simpleforum",
'bootstrapTheme', # Theme "location_field",
'website', # Blechreiz Website in general "scoremanager",
'musicians', # User Management # 'imagestore', # Disabled
'eventplanner', # Event Management ]
'eventplanner_gcal', # Event Management Sync with Google Calendar
'simpleforum', # Messages ( Forum )
'location_field', # custom location field used in Event Management
'scoremanager', # manager of scores, repertoire etc.
# 'imagestore',
# 'sorl.thumbnail',
# 'tagging'
)
IMAGESTORE_TEMPLATE = "website/base.html" IMAGESTORE_TEMPLATE = "website/base.html"
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',), "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
'PAGINATE_BY': 10 "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
} }
GCAL_COUPLING = { GCAL_COUPLING = {
'eventPrefix': 'Blechreiz: ', "eventPrefix": "Blechreiz: ",
'developerKey': 'blechreiz-homepage', "developerKey": "blechreiz-homepage",
'clientId': '34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com', "clientId": "34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com",
'client_secret': 'y4t9XBrJdCODPTO5UvtONWWn', "client_secret": "y4t9XBrJdCODPTO5UvtONWWn",
'credentials_file': DATA_PATH + '/calendarCredentials.dat', "credentials_file": PROJECT_PATH + "/calendarCredentials.dat",
'push_url': "https://blechreiz.bauer.technology/eventplanner_gcal/gcalApiCallback", "push_url": "https://blechreiz.bauer.technology/eventplanner_gcal/gcalApiCallback",
} }
GOOGLE_MAPS_API_KEY = 'AIzaSyCf9Lm5ckjmVd08scTOd7fB1dC_UCoumKg'
GCAL_SYNC_ENABLED = True
CRISPY_TEMPLATE_PACK = 'bootstrap' # Crispy Forms configuration
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
# Logging configuration
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'disable_existing_loggers': False, "disable_existing_loggers": False,
'formatters': { "handlers": {
'verbose': { "file": {
'format': '{levelname} {asctime} {module} {message}', "level": "DEBUG",
'style': '{', "class": "logging.FileHandler",
"filename": PROJECT_PATH + "/eventplanner.log",
}, },
'simple': { "console": {
'format': '{levelname} {asctime} {message}', "level": "DEBUG",
'style': '{', "class": "logging.StreamHandler",
}, },
}, },
'handlers': { "loggers": {
'file': { "eventplanner_gcal": {
'level': 'DEBUG', "handlers": ["file", "console"],
'class': 'logging.FileHandler', "level": "DEBUG",
'filename': DATA_PATH + '/eventplanner.log', "propagate": True,
},
"eventplanner": {
"handlers": ["file", "console"],
"level": "DEBUG",
"propagate": True,
}, },
}, },
'loggers': {
'eventplanner_gcal': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
'eventplanner': {
'handler': ['file'],
'level': 'DEBUG',
'propagate': True,
} }
},
}

View File

@@ -1,28 +1,21 @@
from django.conf.urls import include, url from django.conf import settings
from django.contrib import admin
import simpleforum.views
import eventplanner.urls
import musicians.urls
import website.urls
import scoremanager.urls
import eventplanner_gcal.urls
from . import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path, re_path
admin.autodiscover() from simpleforum import views as simpleforum_views
urlpatterns = [ urlpatterns = [
url(r'^', include(website.urls)), path("", include("website.urls")),
url(r'^events/', include(eventplanner.urls.urlpatterns)), path("events/", include("eventplanner.urls")),
url(r'^musicians/', include(musicians.urls.urlpatterns)), path("musicians/", include("musicians.urls")),
url(r'^scores/', include(scoremanager.urls.urlpatterns)), path("scores/", include("scoremanager.urls")),
url(r'^messages/$', simpleforum.views.message_view), path("messages/", simpleforum_views.message_view),
url(r'^admin/', admin.site.urls), path("admin/", admin.site.urls),
url(r'^location_field/', include('location_field.urls')), path("location_field/", include("location_field.urls")),
url(r'^eventplanner_gcal/', include(eventplanner_gcal.urls)), path("eventplanner_gcal/", include("eventplanner_gcal.urls")),
# url(r'^gallery/', include(imagestore.urls, namespace='imagestore') ), # url(r'^gallery/', include('imagestore.urls', namespace='imagestore')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -1,32 +1,16 @@
""" """
WSGI config for blechreiz project. WSGI config for blechreiz project.
This module contains the WSGI application used by Django's development server It exposes the WSGI callable as a module-level variable named ``application``.
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.
Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
""" """
import os import os
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks from django.core.wsgi import get_wsgi_application
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "blechreiz.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blechreiz.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blechreiz.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application() application = get_wsgi_application()
# Apply WSGI middleware here.
# from helloworld.wsgi import HelloWorldApplication
# application = HelloWorldApplication(application)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,37 +1,17 @@
/* German initialisation for the jQuery UI date picker plugin. */ /**
/* Written by Milian Wolff (mail@milianw.de). */ * German translation for bootstrap-datepicker
( function( factory ) { * Sam Zurcher <sam@orelias.ch>
if ( typeof define === "function" && define.amd ) { */
;(function($){
// AMD. Register as an anonymous module. $.fn.datepicker.dates['de'] = {
define( [ "../widgets/datepicker" ], factory ); days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"],
} else { daysShort: ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam", "Son"],
daysMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
// Browser globals months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
factory( jQuery.datepicker ); monthsShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
} today: "Heute",
}( function( datepicker ) { clear: "Löschen",
weekStart: 1,
datepicker.regional.de = { format: "dd.mm.yyyy"
closeText: "Schließen", };
prevText: "&#x3C;Zurück", }(jQuery));
nextText: "Vor&#x3E;",
currentText: "Heute",
monthNames: [ "Januar","Februar","März","April","Mai","Juni",
"Juli","August","September","Oktober","November","Dezember" ],
monthNamesShort: [ "Jan","Feb","Mär","Apr","Mai","Jun",
"Jul","Aug","Sep","Okt","Nov","Dez" ],
dayNames: [ "Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag" ],
dayNamesShort: [ "So","Mo","Di","Mi","Do","Fr","Sa" ],
dayNamesMin: [ "So","Mo","Di","Mi","Do","Fr","Sa" ],
weekHeader: "KW",
dateFormat: "dd.mm.yy",
firstDay: 1,
isRTL: false,
showMonthAfterYear: false,
yearSuffix: "" };
datepicker.setDefaults( datepicker.regional.de );
return datepicker.regional.de;
} ) );

File diff suppressed because one or more lines are too long

View File

@@ -1,52 +1,64 @@
{% load sekizai_tags staticfiles %} {% load sekizai_tags static %} {% addtoblock "css" strip %}
<link rel="stylesheet" href="{{STATIC_URL}}/css/bootstrap.min.css" />{% endaddtoblock %} {% addtoblock "css" strip %}
<link rel="stylesheet" href="{{STATIC_URL}}/css/bootstrap-responsive.min.css" />
{% addtoblock "css" strip %} <link rel="stylesheet" href="{{STATIC_URL}}css/bootstrap.min.css" > {% endaddtoblock %} {% endaddtoblock %} {% addtoblock "css" strip %}
{% addtoblock "css" strip %} <link rel="stylesheet" href="{{STATIC_URL}}css/bootstrap-responsive.min.css" > {% endaddtoblock %} <link rel="stylesheet" href="{{STATIC_URL}}/css/bootstrap-overrides.css" />{% endaddtoblock %} {% addtoblock "css" strip %}
{% addtoblock "css" strip %} <link rel="stylesheet" href="{{STATIC_URL}}css/bootstrap-overrides.css"> {% endaddtoblock %} <link rel="stylesheet" href="{{STATIC_URL}}/css/theme.css" type="text/css" />{% endaddtoblock %} {% addtoblock "css" strip %}
{% addtoblock "css" strip %} <link rel="stylesheet" href="{{STATIC_URL}}css/theme.css" type="text/css"> {% endaddtoblock %} <link
{% addtoblock "css" strip %} <link rel="stylesheet" href="{{STATIC_URL}}css/index.css" type="text/css" media="screen" /> {% endaddtoblock %} rel="stylesheet"
{% addtoblock "css" strip %} <link href='https://fonts.googleapis.com/css?family=Lato:300,400,700,900,300italic,400italic,700italic,900italic' rel='stylesheet' type='text/css'>{% endaddtoblock %} href="{{STATIC_URL}}/css/index.css"
type="text/css"
{% addtoblock "css" %} media="screen"
/>
{% endaddtoblock %} {% addtoblock "css" strip %}
<link
href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900,300italic,400italic,700italic,900italic"
rel="stylesheet"
type="text/css"
/>{% endaddtoblock %} {% addtoblock "css" %}
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]--> <![endif]-->
{% endaddtoblock %} {% endaddtoblock %} {% addtoblock "js" strip %}
<script src="{{STATIC_URL}}/js/jquery-2.0.3.min.js"></script>
{% endaddtoblock %} {% addtoblock "js" strip %}
<script src="{{STATIC_URL}}/js/bootstrap.min.js"></script>
{% addtoblock "js" strip %} <script src="{{STATIC_URL}}js/jquery-2.0.3.min.js"></script> {% endaddtoblock %} {% endaddtoblock %} {% addtoblock "js" strip %}
{% addtoblock "js" strip %} <script src="{{STATIC_URL}}js/bootstrap.min.js"></script> {% endaddtoblock %} <script src="{{STATIC_URL}}/js/jquery.countdown.min.js"></script>
{% addtoblock "js" strip %} <script src="{{STATIC_URL}}js/jquery.countdown.min.js"></script> {% endaddtoblock %} {% endaddtoblock %} {% addtoblock "js" strip %}
{% addtoblock "js" strip %} <script src="{{STATIC_URL}}js/theme.js"></script> {% endaddtoblock %} <script src="{{STATIC_URL}}/js/theme.js"></script>
{% addtoblock "js" strip %} <script src="{{STATIC_URL}}js/index-slider.js" type="text/javascript" ></script>{% endaddtoblock %} {% endaddtoblock %} {% addtoblock "js" strip %}
{% addtoblock "js" strip %} <script src="{{STATIC_URL}}js/bindWithDelay.js" type="text/javascript" ></script>{% endaddtoblock %} <script src="{{STATIC_URL}}/js/index-slider.js" type="text/javascript"></script>
{% endaddtoblock %} {% addtoblock "js" strip %}
{% addtoblock "js" %} <script
src="{{STATIC_URL}}/js/bindWithDelay.js"
type="text/javascript"
></script>
{% endaddtoblock %} {% addtoblock "js" %}
<script> <script>
function getCookie(name) { function getCookie(name) {
var cookieValue = null; var cookieValue = null;
if (document.cookie && document.cookie != '') { if (document.cookie && document.cookie != "") {
var cookies = document.cookie.split(';'); var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) { for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]); var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want? // Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) { if (cookie.substring(0, name.length + 1) == name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); cookieValue = decodeURIComponent(
cookie.substring(name.length + 1),
);
break; break;
} }
} }
} }
return cookieValue; return cookieValue;
} }
var csrftoken = getCookie('csrftoken'); var csrftoken = getCookie("csrftoken");
function csrfSafeMethod(method) { function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection // these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);
} }
$.ajaxSetup({ $.ajaxSetup({
crossDomain: false, // obviates need for sameOrigin test crossDomain: false, // obviates need for sameOrigin test
@@ -54,9 +66,7 @@ $.ajaxSetup({
if (!csrfSafeMethod(settings.type)) { if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken); xhr.setRequestHeader("X-CSRFToken", csrftoken);
} }
} },
}); });
</script> </script>
{% endaddtoblock %} {% endaddtoblock %}

View File

@@ -1,20 +1,69 @@
#from django.contrib import admin from django.contrib import admin
#from eventplanner.models import Event, EventParticipation
# from .models import Event, EventParticipation
#
#class EventParticipationInline(admin.TabularInline):
# model = EventParticipation class EventParticipationInline(admin.TabularInline):
# extra = 1 """Inline admin for event participations."""
# readonly_fields = ('user',)
# fields = ('user', 'status', 'comment',) model = EventParticipation
# has_add_permission = lambda self, req: False extra = 0
# has_delete_permission = lambda self, req, obj: False readonly_fields = ("user",)
# fields = ("user", "status", "comment")
# template = "eventplanner/admin_tabular.html"
# def has_add_permission(self, request, obj=None):
# return False
#class EventAdmin(admin.ModelAdmin):
# inlines = (EventParticipationInline,) def has_delete_permission(self, request, obj=None):
# return False
#
#admin.site.register(Event, EventAdmin)
@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
"""Admin configuration for Event model."""
list_display = ("title", "type", "date", "time", "location")
list_filter = ("type", "date")
search_fields = ("short_desc", "location", "desc")
date_hierarchy = "date"
ordering = ("-date",)
inlines = (EventParticipationInline,)
fieldsets = (
(
None,
{
"fields": ("type", "short_desc", "date", "end_date"),
},
),
(
"Time",
{
"fields": ("time", "meeting_time"),
},
),
(
"Location",
{
"fields": ("location", "map_location"),
},
),
(
"Description",
{
"fields": ("desc",),
"classes": ("collapse",),
},
),
)
@admin.register(EventParticipation)
class EventParticipationAdmin(admin.ModelAdmin):
"""Admin configuration for EventParticipation model."""
list_display = ("event", "user", "status", "comment")
list_filter = ("status", "event__date")
search_fields = ("user__username", "event__short_desc", "comment")
raw_id_fields = ("event", "user")

View File

@@ -0,0 +1,55 @@
# Generated by Django 5.1.15 on 2026-03-30 19:15
import django.db.models.deletion
import location_field.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Event',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(choices=[('Reh', 'Rehearsal'), ('Conc', 'Concert'), ('Party', 'Party'), ('Travel', 'Travel'), ('Option', 'Option')], default='Reh', max_length=6, verbose_name='type')),
('short_desc', models.CharField(blank=True, max_length=100, null=True, verbose_name='Short Description')),
('location', models.TextField(blank=True, verbose_name='location')),
('map_location', location_field.models.PlainLocationField(based_field=models.TextField(blank=True, verbose_name='location'), blank=True, max_length=63, verbose_name='Location on map', zoom=7)),
('desc', models.TextField(blank=True, verbose_name='description')),
('date', models.DateField(verbose_name='date')),
('time', models.TimeField(blank=True, null=True, verbose_name='time')),
('meeting_time', models.TimeField(blank=True, null=True, verbose_name='meeting_time')),
('end_date', models.DateField(blank=True, null=True, verbose_name='End Date')),
],
options={
'ordering': ['date', 'time'],
},
),
migrations.CreateModel(
name='EventParticipation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('?', '?'), ('Yes', 'Yes'), ('No', 'No'), ('-', '-')], default='?', max_length=3, verbose_name='status')),
('comment', models.CharField(blank=True, max_length=64, verbose_name='comment')),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eventplanner.event', verbose_name='event')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'permissions': (('admin', 'Admin'), ('member', 'Member')),
'unique_together': {('event', 'user')},
},
),
migrations.AddField(
model_name='event',
name='participants',
field=models.ManyToManyField(through='eventplanner.EventParticipation', to=settings.AUTH_USER_MODEL, verbose_name='participants'),
),
]

View File

View File

@@ -1,9 +1,10 @@
from django.db import models
from django.utils.translation import ugettext as _
from django.contrib.auth.models import User, Permission
from django.db.models import Q
from datetime import datetime from datetime import datetime
from django.contrib.auth.models import Permission, User
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from location_field.models import PlainLocationField from location_field.models import PlainLocationField
@@ -14,38 +15,53 @@ class NoNextEventException(Exception):
class Event(models.Model): class Event(models.Model):
EVENT_TYPES = ( EVENT_TYPES = (
('Reh', _('Rehearsal')), ("Reh", _("Rehearsal")),
('Conc', _('Concert')), ("Conc", _("Concert")),
('Party', _('Party')), ("Party", _("Party")),
('Travel', _('Travel')), ("Travel", _("Travel")),
('Option', _('Option')), ("Option", _("Option")),
) )
type = models.CharField(max_length=6, choices=EVENT_TYPES, default='Reh', verbose_name=_("type")) type = models.CharField(
short_desc = models.CharField(null=True, max_length=100, blank=True, verbose_name=_("Short Description")) max_length=6, choices=EVENT_TYPES, default="Reh", verbose_name=_("type")
)
short_desc = models.CharField(
null=True, max_length=100, blank=True, verbose_name=_("Short Description")
)
location = models.TextField(blank=True, verbose_name=_("location")) location = models.TextField(blank=True, verbose_name=_("location"))
map_location = PlainLocationField(blank=True, based_field=location, zoom=7, verbose_name=_("Location on map")) map_location = PlainLocationField(
blank=True, based_field=location, zoom=7, verbose_name=_("Location on map")
)
desc = models.TextField(blank=True, verbose_name=_("description")) desc = models.TextField(blank=True, verbose_name=_("description"))
date = models.DateField(verbose_name=_("date")) date = models.DateField(verbose_name=_("date"))
time = models.TimeField(null=True, blank=True, verbose_name=_("time")) time = models.TimeField(null=True, blank=True, verbose_name=_("time"))
meeting_time = models.TimeField(null=True, blank=True, verbose_name=_("meeting_time")) meeting_time = models.TimeField(
null=True, blank=True, verbose_name=_("meeting_time")
)
end_date = models.DateField(null=True, blank=True, verbose_name=_("End Date")) end_date = models.DateField(null=True, blank=True, verbose_name=_("End Date"))
participants = models.ManyToManyField(User, through='EventParticipation', verbose_name=_("participants")) participants = models.ManyToManyField(
User, through="EventParticipation", verbose_name=_("participants")
)
def __unicode__(self): class Meta:
ordering = ["date", "time"]
def __str__(self):
return self.title return self.title
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# Call the "real" save() method # Call the "real" save() method
super(Event, self).save(*args, **kwargs) super().save(*args, **kwargs)
# Create a "Don't Know" participation for each Musician # Create a "Don't Know" participation for each Musician
for u in User.objects.all(): for u in User.objects.all():
if not u in self.participants.all(): if u not in self.participants.all():
EventParticipation.objects.create(event=self, user=u, status='-', comment='') EventParticipation.objects.create(
event=self, user=u, status="-", comment=""
)
@property @property
def title(self): def title(self):
@@ -64,7 +80,7 @@ class Event(models.Model):
@property @property
def displaydatetime(self): def displaydatetime(self):
if not self.displaytime == None: if self.displaytime is not None:
return datetime.combine(self.date, self.displaytime) return datetime.combine(self.date, self.displaytime)
else: else:
return datetime.combine(self.date, datetime.min.time()) return datetime.combine(self.date, datetime.min.time())
@@ -77,9 +93,13 @@ class Event(models.Model):
if includePreviousFromToday: if includePreviousFromToday:
if eventType == "": if eventType == "":
nextEvents = Event.objects.filter(date__gte=datetime.now()).order_by('date')[:1] nextEvents = Event.objects.filter(date__gte=datetime.now()).order_by(
"date"
)[:1]
else: else:
nextEvents = Event.objects.filter(date__gte=datetime.now(), type=eventType).order_by('date')[:1] nextEvents = Event.objects.filter(
date__gte=datetime.now(), type=eventType
).order_by("date")[:1]
if len(nextEvents) == 0: if len(nextEvents) == 0:
raise NoNextEventException() raise NoNextEventException()
@@ -89,11 +109,13 @@ class Event(models.Model):
maximalNumberOfEventsOnSameDay = 4 maximalNumberOfEventsOnSameDay = 4
nextEvents = [] nextEvents = []
if eventType == "": if eventType == "":
nextEvents = Event.objects.filter(date__gte=datetime.now()).order_by('date')[ nextEvents = Event.objects.filter(date__gte=datetime.now()).order_by(
:maximalNumberOfEventsOnSameDay] "date"
)[:maximalNumberOfEventsOnSameDay]
else: else:
nextEvents = Event.objects.filter(date__gte=datetime.now(), type=eventType).order_by('date')[ nextEvents = Event.objects.filter(
:maximalNumberOfEventsOnSameDay] date__gte=datetime.now(), type=eventType
).order_by("date")[:maximalNumberOfEventsOnSameDay]
if len(nextEvents) == 0: if len(nextEvents) == 0:
raise NoNextEventException() raise NoNextEventException()
@@ -112,28 +134,38 @@ class Event(models.Model):
class EventParticipation(models.Model): class EventParticipation(models.Model):
OPTIONS = (('?', _('?')), OPTIONS = (
('Yes', _('Yes')), ("?", _("?")),
('No', _('No')), ("Yes", _("Yes")),
('-', _('-')) ("No", _("No")),
("-", _("-")),
) )
event = models.ForeignKey(Event, verbose_name=_("event"), on_delete=models.PROTECT) event = models.ForeignKey(Event, on_delete=models.CASCADE, verbose_name=_("event"))
user = models.ForeignKey(User, verbose_name=_("user"), on_delete=models.PROTECT) user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("user"))
status = models.CharField(max_length=3, choices=OPTIONS, default='?', verbose_name=_("status")) status = models.CharField(
max_length=3, choices=OPTIONS, default="?", verbose_name=_("status")
)
comment = models.CharField(max_length=64, blank=True, verbose_name=_("comment")) comment = models.CharField(max_length=64, blank=True, verbose_name=_("comment"))
class Meta:
unique_together = ("event", "user")
permissions = (
("admin", _("Admin")),
("member", _("Member")),
)
def get_username(self): def get_username(self):
return self.user.username return self.user.username
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
prev = EventParticipation.objects.filter(event=self.event, user=self.user) prev = EventParticipation.objects.filter(event=self.event, user=self.user)
if len(prev) == 0: if len(prev) == 0:
super(EventParticipation, self).save(*args, **kwargs) super().save(*args, **kwargs)
else: else:
prev = prev[0] prev = prev[0]
if prev.status != self.status or prev.comment != self.comment: if prev.status != self.status or prev.comment != self.comment:
super(EventParticipation, self).save(*args, **kwargs) super().save(*args, **kwargs)
@staticmethod @staticmethod
def hasUserSetParticipationForAllEvents(user): def hasUserSetParticipationForAllEvents(user):
@@ -142,7 +174,7 @@ class EventParticipation(models.Model):
futurePart = EventParticipation.objects.filter(event__date__gte=datetime.now()) futurePart = EventParticipation.objects.filter(event__date__gte=datetime.now())
notYetEntered = futurePart.filter(user=user).filter(status='-') notYetEntered = futurePart.filter(user=user).filter(status="-")
if len(notYetEntered) > 0: if len(notYetEntered) > 0:
return False return False
else: else:
@@ -150,30 +182,27 @@ class EventParticipation(models.Model):
@staticmethod @staticmethod
def isMember(user): def isMember(user):
return user.has_perm('eventplanner.member') return user.has_perm("eventplanner.member")
@staticmethod @staticmethod
def isAdmin(user): def isAdmin(user):
return user.has_perm('eventplanner.admin') return user.has_perm("eventplanner.admin")
@staticmethod @staticmethod
def members(): def members():
perm = Permission.objects.get(codename='member') perm = Permission.objects.get(codename="member")
f = User.objects.filter(Q(groups__permissions=perm) | Q(user_permissions=perm)).distinct() f = User.objects.filter(
return f.order_by('musician__position') Q(groups__permissions=perm) | Q(user_permissions=perm)
).distinct()
return f.order_by("musician__position")
@staticmethod @staticmethod
def get_or_create(user, event): def get_or_create(user, event):
try: try:
result = EventParticipation.objects.get(event=event, user=user) result = EventParticipation.objects.get(event=event, user=user)
except EventParticipation.DoesNotExist: except EventParticipation.DoesNotExist:
result = EventParticipation.objects.create(event=event, user=user, status='-', comment='') result = EventParticipation.objects.create(
event=event, user=user, status="-", comment=""
)
return result return result
class Meta:
unique_together = ("event", "user")
permissions = (
("admin", _("Admin")),
("member", _("Member")),
)

View File

@@ -1,19 +1,24 @@
from rest_framework import serializers from rest_framework import serializers
from .models import EventParticipation, Event
from .models import Event, EventParticipation
class ParticipationSerializer(serializers.ModelSerializer): class ParticipationSerializer(serializers.ModelSerializer):
event = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Event.objects.all()) event = serializers.PrimaryKeyRelatedField(queryset=Event.objects.all())
user = serializers.Field(source='get_username') user = serializers.CharField(source="get_username", read_only=True)
status = serializers.CharField(source='status', required=False) status = serializers.CharField(required=False)
def get_identity(self, data):
""" This hook is required for bulk update. """
try:
return data.get('event', None), data.get('user')
except AttributeError:
return None
class Meta: class Meta:
model = EventParticipation model = EventParticipation
fields = ('event', 'user', 'status', 'comment') fields = ("event", "user", "status", "comment")
def create(self, validated_data):
# Remove the get_username source field as it's read-only
validated_data.pop("get_username", None)
return super().create(validated_data)
def update(self, instance, validated_data):
instance.status = validated_data.get("status", instance.status)
instance.comment = validated_data.get("comment", instance.comment)
instance.save()
return instance

View File

@@ -1,7 +1,9 @@
from datetime import datetime from datetime import datetime
from .models import Event, EventParticipation, NoNextEventException
from musicians.models import Musician from musicians.models import Musician
from .models import Event, EventParticipation, NoNextEventException
def addEventCountdownForNextEventToContext(context, username, eventType=""): def addEventCountdownForNextEventToContext(context, username, eventType=""):
"""Returns an object that has to be added to the render context on the page where the countdown """Returns an object that has to be added to the render context on the page where the countdown
@@ -12,15 +14,16 @@ def addEventCountdownForNextEventToContext(context, username, eventType=""):
except NoNextEventException: except NoNextEventException:
return return
countdown = dict() countdown = {}
if EventParticipation.isMember(username): if EventParticipation.isMember(username):
part = EventParticipation.objects.filter(user=username).filter(event=nextEvent) part = EventParticipation.objects.filter(user=username).filter(event=nextEvent)
countdown['participation'] = part[0].status if part.exists():
countdown["participation"] = part[0].status
eventTime = nextEvent.displaydatetime eventTime = nextEvent.displaydatetime
countdown['event'] = nextEvent countdown["event"] = nextEvent
countdown['epoch'] = int((eventTime - datetime.now()).total_seconds() * 1000) countdown["epoch"] = int((eventTime - datetime.now()).total_seconds() * 1000)
context["countdown"] = countdown context["countdown"] = countdown
@@ -34,17 +37,22 @@ def addEventRouteForNextEventToContext(context, username, eventType=""):
except NoNextEventException: except NoNextEventException:
return return
routeInfo = dict() routeInfo = {}
routeInfo['event'] = nextEvent routeInfo["event"] = nextEvent
musician = Musician.objects.get(user=username); try:
routeInfo['origin'] = musician.street + ", " + str(musician.zip_code) + " " + musician.city musician = Musician.objects.get(user=username)
routeInfo["origin"] = (
musician.street + ", " + str(musician.zip_code) + " " + musician.city
)
except Musician.DoesNotExist:
routeInfo["origin"] = ""
if nextEvent.map_location: if nextEvent.map_location:
# map_location has format "lat,longitute,zoomlevel" # map_location has format "lat,longitude,zoomlevel"
routeInfo['destination'] = ",".join(nextEvent.map_location.split(",")[:2]) routeInfo["destination"] = ",".join(nextEvent.map_location.split(",")[:2])
else: else:
routeInfo['destination'] = nextEvent.location routeInfo["destination"] = nextEvent.location
context["route"] = routeInfo context["route"] = routeInfo

View File

@@ -1,4 +1,4 @@
{% load i18n admin_static admin_modify %} {% load i18n static admin_modify %}
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"> <div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}"> <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
{{ inline_admin_formset.formset.management_form }} {{ inline_admin_formset.formset.management_form }}

View File

@@ -1,15 +1,14 @@
{% load sekizai_tags staticfiles %} {% load sekizai_tags static %} {% addtoblock "css" strip %}<link
rel="stylesheet"
type="text/css"
href="{{STATIC_URL}}/css/lib/animate.css"
{% addtoblock "css" strip %}<link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/lib/animate.css" media="screen, projection">{% endaddtoblock %} media="screen, projection"
{% addtoblock "css" strip %}<link rel="stylesheet" href="{{STATIC_URL}}css/coming-soon.css" type="text/css" media="screen" />{% endaddtoblock %} />{% endaddtoblock %} {% addtoblock "css" strip %}<link
rel="stylesheet"
{% if countdown %} href="{{STATIC_URL}}/css/coming-soon.css"
type="text/css"
media="screen"
/>{% endaddtoblock %} {% if countdown %} {% addtoblock "js" %}
{% addtoblock "js" %}
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
function callback(event) { function callback(event) {
@@ -30,28 +29,21 @@
</script> </script>
{% endaddtoblock %} {% endaddtoblock %}
<div id="coming_soon"> <div id="coming_soon">
<div class="head"> <div class="head">
<div class="container"> <div class="container">
<div class="span6 text"> <div class="span6 text">
<h4>Der nächste Termin:</h4> <h4>Der nächste Termin:</h4>
<p> <p>
{{countdown.event.title}} am {{countdown.event.date | date:"D, d.m.y" }} {{countdown.event.title}} am {{countdown.event.date | date:"D, d.m.y" }} {% if coundown.event.displaytime %} um
{% if coundown.event.displaytime %} um {{countdown.event.displaytime | time:"H:i" }} Uhr {% endif %} {{countdown.event.displaytime | time:"H:i" }} Uhr {% endif %} {% if coundown.event.location %} in
{% if coundown.event.location %} in <em>{{countdown.event.location}} </em> {% endif %} <em>{{countdown.event.location}} </em> {% endif %}
<br /> <br />
{% if 'participation' in countdown %} {% if 'participation' in countdown %} {% if countdown.participation == "?"%} Du hast dich noch nicht
{% if countdown.participation == "?" %} für diesen Termin eingetragen! {% elif countdown.participation == "Yes"%} Du hast für diesen
Du hast dich noch nicht für diesen Termin eingetragen! Termin zugesagt. {% elif countdown.participation == "No" %}
{% elif countdown.participation == "Yes" %} Du hast für diesen Termin abgesagt. {%endif %} {% endif %}
Du hast für diesen Termin zugesagt.
{% elif countdown.participation == "No" %}
Du hast für diesen Termin abgesagt.
{%endif %}
{% endif %}
</p> </p>
</div> </div>
<div class="span6 count" id="clock"> <div class="span6 count" id="clock">

View File

@@ -1,12 +1,4 @@
{% extends "website/base.html" %} {% extends "website/base.html" %} {% load sekizai_tags static %} {% load crispy_forms_tags %} {% block content %} {{ form.media }}
{% load sekizai_tags staticfiles %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container"> <div class="container">
<div class="row"> <div class="row">
@@ -14,39 +6,48 @@
<h3>Termin bearbeiten</h3> <h3>Termin bearbeiten</h3>
{% crispy form %} {% crispy form %}
</div> </div>
</div> </div>
</div> </div>
<!-- Datepicker --> <!-- Datepicker -->
{% addtoblock "css" strip %} {% addtoblock "css" strip %}<link
<link rel="stylesheet" href="{{STATIC_URL}}css/jquery-ui-1.12.1.min.css" type="text/css" media="screen" /> rel="stylesheet"
{% endaddtoblock %} href="{{STATIC_URL}}/css/datepicker.css"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/timepicker.css"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/jquery-ui-1.8.21.custom.css"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "js" %}
<script src="{{STATIC_URL}}/js/bootstrap-timepicker.js"></script>
<script src="{{STATIC_URL}}/js/bootstrap-datepicker.js"></script>
<script src="{{STATIC_URL}}/js/bootstrap-datepicker.de.js"></script>
{% addtoblock "js" %}
<script src="{{STATIC_URL}}js/jquery-ui-1.12.1.min.js"></script>
<script src="{{STATIC_URL}}js/bootstrap-datepicker.de.js"></script>
<script> <script>
$(document).ready(function(){ $(document).ready(function(){
$.datepicker.setDefaults( $('.dateinput').datepicker({
$.extend( format: "dd.mm.yyyy",
{'dateFormat':'dd-mm-yy'}, weekStart: 1,
$.datepicker.regional['de'] todayBtn: "linked",
) language: "de",
); todayHighlight: true,
startDate: "{% now "SHORT_DATE_FORMAT" %}",
});
$('.dateinput').datepicker();
/*
$('.timeinput').addClass('input-small').wrap('<div class="input-append bootstrap-timepicker">') $('.timeinput').addClass('input-small').wrap('<div class="input-append bootstrap-timepicker">')
$(".input-append").append( '<span class="add-on"><i class="icon-time"></i></span>' ) $(".input-append").append( '<span class="add-on"><i class="icon-time"></i></span>' )
@@ -56,7 +57,6 @@ $(document).ready(function(){
showMeridian: false, showMeridian: false,
defaultTime: false defaultTime: false
}); });
*/
$('form').submit(function() { $('form').submit(function() {
if ( $("#id_type").val() != "Option" && $("#id_location").val().trim() == "" ) { if ( $("#id_type").val() != "Option" && $("#id_location").val().trim() == "" ) {
@@ -128,11 +128,4 @@ $(document).ready(function(){
} ); } );
</script> </script>
{% endaddtoblock %} {% endaddtoblock %} {% endblock %}
{% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "website/base.html" %} {% extends "website/base.html" %}
{% load sekizai_tags staticfiles %} {% load sekizai_tags static %}
{% block content %} {% block content %}
@@ -118,7 +118,7 @@
request = $.ajax( { request = $.ajax( {
type: "PUT", type: "PUT",
url: "{% url 'event_api' %}", url: "{% url 'eventplanner:event_api' %}",
contentType: "application/json", contentType: "application/json",
data: JSON.stringify(putObject), data: JSON.stringify(putObject),
success: function() { $("#saving").html("Ok"); }, success: function() { $("#saving").html("Ok"); },
@@ -139,7 +139,7 @@
$.ajax( { $.ajax( {
type: "PUT", type: "PUT",
url: "{% url 'event_api' %}", url: "{% url 'eventplanner:event_api' %}",
contentType: "application/json", contentType: "application/json",
data: JSON.stringify(putObject), data: JSON.stringify(putObject),
success: function() { $("#saving").html("Ok"); } success: function() { $("#saving").html("Ok"); }
@@ -181,6 +181,14 @@
<div class="span12"> <div class="span12">
<div class="alert alert-info">
Es gibt jetzt einen vierten Zustand "nicht eingetragen", angezeigt durch nur
blaue Buttons.<br>
"?" bedeutet jetzt: Ich habe mich eingetragen weiss aber noch nicht ob ich komme.
Bitte nur selten benutzen!
</div>
<div class="box-content"> <div class="box-content">
<table id="eventTable" class="table table-striped"> <table id="eventTable" class="table table-striped">

View File

@@ -1,6 +1,6 @@
{% extends "website/base.html" %} {% extends "website/base.html" %}
{% load sekizai_tags staticfiles %} {% load sekizai_tags static %}
{% block content %} {% block content %}
@@ -104,7 +104,7 @@
$.ajax( { $.ajax( {
type: "PUT", type: "PUT",
url: "{% url 'event_api' %}", url: "{% url 'eventplanner:event_api' %}",
contentType: "application/json", contentType: "application/json",
data: JSON.stringify(arr) data: JSON.stringify(arr)
}); });
@@ -143,6 +143,13 @@
<h2>Termine</h2> <h2>Termine</h2>
<div class="alert alert-info">
Es gibt jetzt einen vierten Zustand "nicht eingetragen", angezeigt durch "-".<br>
"?" bedeutet jetzt: Ich habe mich eingetragen weiss aber noch nicht ob ich komme.
Bitte nur selten benutzen!
</div>
<div class="box-content"> <div class="box-content">
<table class="table table-striped"> <table class="table table-striped">

View File

@@ -1,27 +1,17 @@
{% comment %} {% comment %} Displays google map with directions to next conert Context:
Displays google map with directions to next conert Coordinates or textual adresses: {{route.origin}} {{route.destination}} Event
object: {{route.event}} {% endcomment %} {% load sekizai_tags static %}
Context: {% if route %} {% addtoblock "css" strip %}<link
Coordinates or textual adresses: rel="stylesheet"
{{route.origin}} href="{{STATIC_URL}}/css/concert_route.css"
{{route.destination}} type="text/css"
Event object: media="screen"
{{route.event}} />{% endaddtoblock %} {% addtoblock "js" strip %}
{% endcomment %} <script
type="text/javascript"
src="//maps.google.com/maps/api/js?sensor=false&amp;language=de"
></script>
{% endaddtoblock %} {% addtoblock "js" %}
{% load sekizai_tags staticfiles %}
{% if route %}
{% addtoblock "css" strip %}<link rel="stylesheet" href="{{STATIC_URL}}css/concert_route.css" type="text/css" media="screen" />{% endaddtoblock %}
{% addtoblock "js" strip %}<script type="text/javascript" src="//maps.google.com/maps/api/js?key={{GOOGLE_MAPS_API_KEY}}&amp;sensor=false&amp;language=de"></script>{% endaddtoblock %}
{% addtoblock "js" %}
<script type="text/javascript"> <script type="text/javascript">
function OpenWindowControl(controlDiv, map) { function OpenWindowControl(controlDiv, map) {
@@ -163,9 +153,6 @@
</script> </script>
{% endaddtoblock %} {% endaddtoblock %}
<div id="concert_route"> <div id="concert_route">
<div id="map"></div> <div id="map"></div>
<div id="map_box" class="row map"> <div id="map_box" class="row map">
@@ -186,23 +173,43 @@
</table> </table>
--> -->
<table class="table table-striped table-condensed"> <table class="table table-striped table-condensed">
<tr><td>Ort: </td> <td> {{route.event.location}} </td> </tr> <tr>
<tr><td>Datum: </td> <td> {{route.event.date | date:"D, d.m.y" }} </td> </tr> <td>Ort:</td>
<tr><td>Uhrzeit: </td> <td> {{route.event.time | time:"H:i" }} Uhr </td> </tr> <td>{{route.event.location}}</td>
{% if route.event.meeting_time %} <tr><td>Treffen um: </td> <td> {{route.event.meeting_time | time:"H:i" }} Uhr </td> </tr> {% endif %} </tr>
<tr> <td> Fahrzeit:</td> <td> <span id="route_duration"></span> </td> </tr> <tr>
<tr> <td> Strecke: </td> <td> <span id="route_distance"></span> </td> </tr> <td>Datum:</td>
<td>{{route.event.date | date:"D, d.m.y" }}</td>
</tr>
<tr>
<td>Uhrzeit:</td>
<td>{{route.event.time | time:"H:i" }} Uhr</td>
</tr>
{% if route.event.meeting_time %}
<tr>
<td>Treffen um:</td>
<td>
{{route.event.meeting_time | time:"H:i" }}
Uhr
</td>
</tr>
{% endif %}
<tr>
<td>Fahrzeit:</td>
<td><span id="route_duration"></span></td>
</tr>
<tr>
<td>Strecke:</td>
<td><span id="route_distance"></span></td>
</tr>
</table> </table>
<a class="btn">Schliessen</a> <a class="btn">Schliessen</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div>

View File

@@ -1,15 +1,21 @@
from django.conf.urls import url from django.urls import path, re_path
from django.contrib.auth.decorators import permission_required
from eventplanner.views import events_grid, eventplanning, event_api, EventUpdate, EventCreate, deleteEvent from . import views
app_name = "eventplanner"
urlpatterns = [ urlpatterns = [
url(r'^$', eventplanning), path("", views.eventplanning, name="eventplanning"),
url(r'^grid$', events_grid), path("grid/", views.events_grid, name="events_grid"),
url(r'^planning$', eventplanning), path("delete/<int:pk>/", views.deleteEvent, name="delete_event"),
url(r'^(?P<pk>\d+)$', permission_required('eventplanner.change_event')(EventUpdate.as_view())), path("event/<int:pk>/", views.EventUpdate.as_view(), name="event_update"),
url(r'^add$', permission_required('eventplanner.add_event')(EventCreate.as_view())), path("event/create/", views.EventCreate.as_view(), name="event_create"),
url(r'^(?P<pk>\d+)/delete$', permission_required('eventplanner.delete_event')(deleteEvent)), # API endpoints
url(r'^api/', event_api, name="event_api"), path("api/", views.event_api, name="event_api"),
url(r'^api/(\w+)/$', event_api, name="event_api_per_user"), path("api/<str:username>/", views.event_api, name="event_api_user"),
url(r'^api/(\w+)/(\d+)$', event_api, name="event_api_per_user_event"), path(
"api/<str:username>/<int:eventId>/",
views.event_api,
name="event_api_user_event",
),
] ]

View File

@@ -1,29 +1,30 @@
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.forms.models import ModelForm
from django.forms import TextInput, TimeInput
from .models import Event, EventParticipation
from .serializers import ParticipationSerializer
import datetime import datetime
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit from crispy_forms.layout import Submit
from django.forms import TextInput
from django.forms.models import ModelForm
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.views.generic.edit import CreateView, UpdateView
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from location_field.widgets import LocationWidget
from .models import Event, EventParticipation
from .serializers import ParticipationSerializer
# ---------------------------------------- API --------------------------------------------------------- # ---------------------------------------- API ---------------------------------------------------------
@api_view(['GET', 'PUT']) @api_view(["GET", "PUT"])
def event_api(request, username=None, eventId=None): def event_api(request, username=None, eventId=None):
try: try:
participationQs = EventParticipation.objects.filter(event__date__gte=datetime.date.today()) participationQs = EventParticipation.objects.filter(
event__date__gte=datetime.date.today()
)
if username: if username:
participationQs = EventParticipation.objects.filter(user__username=username) participationQs = EventParticipation.objects.filter(user__username=username)
if eventId: if eventId:
@@ -31,28 +32,34 @@ def event_api(request, username=None, eventId=None):
except EventParticipation.DoesNotExist: except EventParticipation.DoesNotExist:
return HttpResponse(status=404) return HttpResponse(status=404)
if request.method == 'GET': if request.method == "GET":
serializer = ParticipationSerializer(participationQs) serializer = ParticipationSerializer(participationQs, many=True)
return Response(serializer.data) return Response(serializer.data)
elif request.method == 'PUT': elif request.method == "PUT":
serializer = ParticipationSerializer(participationQs, data=request.DATA, many=True) serializer = ParticipationSerializer(data=request.data, many=True)
if serializer.is_valid(): if serializer.is_valid():
for serializedObject in serializer.object: for item in serializer.validated_data:
if not (EventParticipation.isMember(request.user) or EventParticipation.isAdmin(request.user)): event = item.get("event")
user_obj = item.get("user")
if not (
EventParticipation.isMember(request.user)
or EventParticipation.isAdmin(request.user)
):
return Response(status=status.HTTP_403_FORBIDDEN) return Response(status=status.HTTP_403_FORBIDDEN)
if serializedObject.user != request.user: if user_obj != request.user:
if not EventParticipation.isAdmin(request.user): if not EventParticipation.isAdmin(request.user):
return Response(status=status.HTTP_403_FORBIDDEN) return Response(status=status.HTTP_403_FORBIDDEN)
serializer.save() serializer.save()
return Response(serializer.data) return Response(serializer.data)
else: else:
return Response(status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# ------------------------------------ Normal Views ---------------------------------------------------- # ------------------------------------ Normal Views ----------------------------------------------------
def eventplanning(request): def eventplanning(request):
""" """
View for a specific user, to edit his events View for a specific user, to edit his events
@@ -62,27 +69,33 @@ def eventplanning(request):
return events_grid(request) return events_grid(request)
# All events in the future sorted by date # All events in the future sorted by date
all_future_events = list(Event.objects.filter(date__gte=datetime.date.today()).order_by('date')) all_future_events = list(
Event.objects.filter(date__gte=datetime.date.today()).order_by("date")
)
for e in all_future_events: for e in all_future_events:
e.participation = EventParticipation.get_or_create(event=e, user=request.user) e.participation = EventParticipation.get_or_create(event=e, user=request.user)
context = {'events': all_future_events} context = {"events": all_future_events}
return render(request, 'eventplanner/eventplanning_view.html', context) return render(request, "eventplanner/eventplanning_view.html", context)
def events_grid(request): def events_grid(request):
usernames = [u.username for u in EventParticipation.members()] usernames = [u.username for u in EventParticipation.members()]
all_future_events = list(Event.objects.filter(date__gte=datetime.date.today()).order_by('date')) all_future_events = list(
Event.objects.filter(date__gte=datetime.date.today()).order_by("date")
)
for e in all_future_events: for e in all_future_events:
e.participation = [EventParticipation.get_or_create(event=e, user=u) for u in EventParticipation.members()] e.participation = [
EventParticipation.get_or_create(event=e, user=u)
for u in EventParticipation.members()
]
context = {'events': all_future_events, context = {"events": all_future_events, "usernames": usernames}
'usernames': usernames}
return render(request, 'eventplanner/events_grid.html', context) return render(request, "eventplanner/events_grid.html", context)
def deleteEvent(request, pk): def deleteEvent(request, pk):
@@ -93,27 +106,30 @@ def deleteEvent(request, pk):
# ------------------------------------ Detail Views ---------------------------------------------------- # ------------------------------------ Detail Views ----------------------------------------------------
from django.views.generic.edit import UpdateView, CreateView
from location_field.widgets import LocationWidget
class EventForm(ModelForm): class EventForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper() self.helper = FormHelper()
self.helper.form_class = 'form-horizontal' self.helper.form_class = "form-horizontal"
self.helper.add_input(Submit('submit', 'Speichern')) self.helper.add_input(Submit("submit", "Speichern"))
super(EventForm, self).__init__(*args, **kwargs)
class Meta: class Meta:
model = Event model = Event
fields = ['type', 'short_desc', 'date', 'end_date', 'time', 'meeting_time', 'location', 'map_location', fields = [
'desc', ] "type",
"short_desc",
"date",
"end_date",
"time",
"meeting_time",
"location",
"map_location",
"desc",
]
widgets = { widgets = {
'time': TimeInput(format='%H:%M'), "location": TextInput(),
'location': TextInput(), "map_location": LocationWidget(),
'map_location': LocationWidget(),
} }
@@ -121,11 +137,11 @@ class EventUpdate(UpdateView):
form_class = EventForm form_class = EventForm
model = Event model = Event
template_name_suffix = "_update_form" template_name_suffix = "_update_form"
success_url = '.' success_url = "."
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['viewtype'] = "update" context["viewtype"] = "update"
return context return context
@@ -133,9 +149,9 @@ class EventCreate(CreateView):
form_class = EventForm form_class = EventForm
model = Event model = Event
template_name_suffix = "_update_form" template_name_suffix = "_update_form"
success_url = '.' success_url = "."
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['viewtype'] = "create" context["viewtype"] = "create"
return context return context

View File

@@ -0,0 +1,3 @@
# eventplanner_gcal app
# Signals are loaded in apps.py AppConfig.ready()
default_app_config = "eventplanner_gcal.apps.EventplannerGcalConfig"

14
eventplanner_gcal/apps.py Normal file
View File

@@ -0,0 +1,14 @@
from django.apps import AppConfig
class EventplannerGcalConfig(AppConfig):
"""App configuration for eventplanner_gcal."""
name = "eventplanner_gcal"
verbose_name = "Event Planner Google Calendar Integration"
default_auto_field = "django.db.models.BigAutoField"
def ready(self):
"""Import signal handlers when the app is ready."""
# Import signals to register them
from . import signals # noqa: F401

View File

@@ -1,70 +1,112 @@
import logging """
import httplib2 Google Calendar synchronization module.
This module handles synchronization between the local event database
and Google Calendar, including push notifications for real-time updates.
"""
import datetime import datetime
import logging
import time import time
from django.conf import settings
from django.contrib.auth.models import User
from eventplanner.models import Event, EventParticipation from eventplanner.models import Event, EventParticipation
from eventplanner_gcal.models import GCalMapping, GCalPushChannel, UserGCalCoupling from eventplanner_gcal.models import GCalMapping, GCalPushChannel, UserGCalCoupling
# noinspection PyUnresolvedReferences,PyUnresolvedReferences
from apiclient.http import BatchHttpRequest
from builtins import str as text # python2 and python3
from django.conf import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Module-level service object cache
_service_object = None
# ---------------------------------- Authentication using oauth2 -----------------------------------------------------
def create_gcal_service_object(): def create_gcal_service_object():
"""Creates a Google API service object. This object is required whenever a Google API call is made""" """
from oauth2client.file import Storage Creates a Google API service object.
# noinspection PyUnresolvedReferences
from apiclient.discovery import build
gcal_settings = settings.GCAL_COUPLING This object is required whenever a Google API call is made.
Uses the new google-auth library instead of oauth2client.
"""
try:
import os
import pickle
storage = Storage(gcal_settings['credentials_file']) from google.auth.transport.requests import Request
credentials = storage.get() from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
logger.debug("Credentials", credentials) from googleapiclient.discovery import build
if credentials is None or credentials.invalid is True: except ImportError as e:
# flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) logger.error(f"Required Google API libraries not installed: {e}")
logger.error("Unable to initialize Google Calendar coupling. Check your settings!")
return None return None
http = httplib2.Http() gcal_settings = settings.GCAL_COUPLING
http = credentials.authorize(http) credentials_file = gcal_settings["credentials_file"]
res = build(serviceName='calendar', version='v3',
http=http, developerKey=gcal_settings['developerKey'])
if res is None: creds = None
logger.error("Authentication at Google API failed. Check your settings!")
return res # Try to load existing credentials
if os.path.exists(credentials_file):
try:
with open(credentials_file, "rb") as token:
creds = pickle.load(token)
except Exception as e:
logger.warning(f"Could not load credentials from {credentials_file}: {e}")
# Check if credentials are valid
if creds and creds.expired and creds.refresh_token:
try:
creds.refresh(Request())
# Save refreshed credentials
with open(credentials_file, "wb") as token:
pickle.dump(creds, token)
except Exception as e:
logger.error(f"Failed to refresh credentials: {e}")
creds = None
if not creds or not creds.valid:
logger.error(
"Invalid or missing Google Calendar credentials. "
"Please run the credential setup process."
)
return None
try:
service = build("calendar", "v3", credentials=creds)
return service
except Exception as e:
logger.error(f"Failed to build Google Calendar service: {e}")
return None
def get_service_object(): def get_service_object():
if get_service_object.__serviceObject is None: """Get or create the Google Calendar service object."""
get_service_object.__serviceObject = create_gcal_service_object() global _service_object
if _service_object is None:
return get_service_object.__serviceObject _service_object = create_gcal_service_object()
return _service_object
get_service_object.__serviceObject = None def reset_service_object():
"""Reset the cached service object (useful for testing or credential refresh)."""
global _service_object
_service_object = None
# --------------------- Building GCal event representation ---------------------------------------------------------- # --------------------- Building GCal event representation ------------------------------------
def build_gcal_attendees_obj(event): def build_gcal_attendees_obj(event):
"""Builds an attendees object that is inserted into the GCal event. """
Attendees are all users that have a google mail address.""" Builds an attendees object that is inserted into the GCal event.
Attendees are all users that have a Google mail address.
"""
result = [] result = []
for userMapping in UserGCalCoupling.objects.all(): for user_mapping in UserGCalCoupling.objects.all():
u = userMapping.user u = user_mapping.user
# No get or create here, since a create would trigger another synchronization
# participation = EventParticipation.get_or_create( u, event )
try: try:
participation = EventParticipation.objects.get(event=event, user=u) participation = EventParticipation.objects.get(event=event, user=u)
local_status = participation.status local_status = participation.status
@@ -76,19 +118,18 @@ def build_gcal_attendees_obj(event):
status = "needsAction" status = "needsAction"
if local_status == "?": if local_status == "?":
status = "tentative" status = "tentative"
elif local_status == 'Yes': elif local_status == "Yes":
status = "accepted" status = "accepted"
elif local_status == 'No': elif local_status == "No":
status = "declined" status = "declined"
o = { attendee = {
'id': userMapping.email, "email": user_mapping.email,
'email': userMapping.email, "displayName": u.username,
'displayName': u.username, "comment": local_comment,
'comment': local_comment, "responseStatus": status,
'responseStatus': status,
} }
result.append(o) result.append(attendee)
return result return result
@@ -96,11 +137,14 @@ def build_gcal_attendees_obj(event):
def build_gcal_event(event, timezone="Europe/Berlin"): def build_gcal_event(event, timezone="Europe/Berlin"):
"""Builds a GCal event using a local event.""" """Builds a GCal event using a local event."""
def create_date_time_obj(date, time_obj): def create_datetime_obj(date, time_val):
if time_obj is None: if time_val is None:
return {'date': text(date), 'timeZone': timezone} return {"date": str(date), "timeZone": timezone}
else: else:
return {'dateTime': text(date) + 'T' + text(time_obj), 'timeZone': timezone} return {
"dateTime": f"{date}T{time_val}",
"timeZone": timezone,
}
start_date = event.date start_date = event.date
end_date = event.end_date end_date = event.end_date
@@ -116,196 +160,285 @@ def build_gcal_event(event, timezone="Europe/Berlin"):
else: else:
end_time = datetime.time(22, 30) end_time = datetime.time(22, 30)
g_location = text(event.location) g_location = str(event.location)
if event.map_location: if event.map_location:
# Map location has the following format: latitude,longitude,zoomlevel # Map location has the following format: latitude,longitude,zoomlevel
# the first two are needed # the first two are needed
s = event.map_location.split(",") parts = event.map_location.split(",")
g_location = text("%s,%s" % (s[0], s[1])) if len(parts) >= 2:
g_location = f"{parts[0]},{parts[1]}"
gcal_settings = settings.GCAL_COUPLING
return { return {
'summary': text(settings.GCAL_COUPLING['eventPrefix'] + event.title), "summary": gcal_settings["eventPrefix"] + event.title,
'description': text(event.desc), "description": str(event.desc),
'location': g_location, "location": g_location,
'start': create_date_time_obj(start_date, start_time), "start": create_datetime_obj(start_date, start_time),
'end': create_date_time_obj(end_date, end_time), "end": create_datetime_obj(end_date, end_time),
'extendedProperties': { "extendedProperties": {
'private': { "private": {
'blechreizEvent': 'true', "blechreizEvent": "true",
'blechreizID': event.id, "blechreizID": str(event.id),
} }
}, },
'attendees': build_gcal_attendees_obj(event), "attendees": build_gcal_attendees_obj(event),
} }
# ------------------------------ Callback Functions ------------------------------------------------------------------- # ------------------------------ Callback Functions ------------------------------------------------
def on_gcal_event_created(_, response, exception=None):
"""Callback function for created events to enter new gcal id in the mapping table""" def on_gcal_event_created(request_id, response, exception=None):
"""Callback function for created events to enter new gcal id in the mapping table."""
if exception is not None: if exception is not None:
logger.error("on_gcal_event_created: Exception " + str(exception)) logger.error(f"Error creating GCal event: {exception}")
raise exception raise exception
google_id = response['id'] google_id = response["id"]
django_id = response['extendedProperties']['private']['blechreizID'] django_id = response["extendedProperties"]["private"]["blechreizID"]
mapping = GCalMapping(gcal_id=google_id, event=Event.objects.get(pk=django_id))
try:
event = Event.objects.get(pk=django_id)
mapping = GCalMapping(gcal_id=google_id, event=event)
mapping.save() mapping.save()
logger.info(f"Created mapping: GCal {google_id} <-> Event {django_id}")
except Event.DoesNotExist:
logger.error(f"Event {django_id} not found when creating GCal mapping")
# ------------------------------ GCal Api Calls -------------------------------------------------------------------- # ------------------------------ GCal Api Calls -------------------------------------------------
def get_all_gcal_events(service, from_now=False): def get_all_gcal_events(service, from_now=False):
"""Retrieves all gcal events with custom property blechreizEvent=True i.e. all """
events that have been created by this script.""" Retrieves all gcal events with custom property blechreizEvent=True.
These are all events that have been created by this script.
"""
if from_now: if from_now:
now = datetime.datetime.now() now = datetime.datetime.now()
min_time = now.strftime("%Y-%m-%dT%H:%M:%S-00:00") min_time = now.strftime("%Y-%m-%dT%H:%M:%S-00:00")
else: else:
min_time = '2000-01-01T00:00:00-00:00' min_time = "2000-01-01T00:00:00-00:00"
events = service.events().list( try:
calendarId='primary', events = (
service.events()
.list(
calendarId="primary",
singleEvents=True, singleEvents=True,
maxResults=1000, maxResults=1000,
orderBy='startTime', orderBy="startTime",
timeMin=min_time, timeMin=min_time,
timeMax='2100-01-01T00:00:00-00:00', timeMax="2100-01-01T00:00:00-00:00",
privateExtendedProperty='blechreizEvent=true', privateExtendedProperty="blechreizEvent=true",
).execute() )
return events['items'] .execute()
)
return events.get("items", [])
except Exception as e:
logger.error(f"Failed to retrieve GCal events: {e}")
return []
def create_gcal_event(service, event, timezone="Europe/Berlin"): def create_gcal_event_request(service, event, timezone="Europe/Berlin"):
"""Creates a new gcal event using a local event""" """Creates a request to create a new gcal event using a local event."""
google_event = build_gcal_event(event, timezone) google_event = build_gcal_event(event, timezone)
return service.events().insert(calendarId='primary', body=google_event) return service.events().insert(calendarId="primary", body=google_event)
def update_gcal_event(service, event, timezone="Europe/Berlin"): def update_gcal_event_request(service, event, timezone="Europe/Berlin"):
"""Updates an existing gcal event, using a local event""" """Creates a request to update an existing gcal event using a local event."""
google_event = build_gcal_event(event, timezone) google_event = build_gcal_event(event, timezone)
try: try:
mapping = GCalMapping.objects.get(event=event) mapping = GCalMapping.objects.get(event=event)
except GCalMapping.DoesNotExist: except GCalMapping.DoesNotExist:
return create_gcal_event(service, event, timezone) return create_gcal_event_request(service, event, timezone)
return service.events().patch(calendarId='primary', eventId=mapping.gcal_id, body=google_event) return service.events().patch(
calendarId="primary", eventId=mapping.gcal_id, body=google_event
)
def delete_gcal_event(service, event): def delete_gcal_event_request(service, event):
"""Deletes gcal that belongs to the given local event""" """Creates a request to delete gcal event that belongs to the given local event."""
try:
mapping = GCalMapping.objects.get(event=event) mapping = GCalMapping.objects.get(event=event)
gcal_id = mapping.gcal_id gcal_id = mapping.gcal_id
mapping.delete() mapping.delete()
return service.events().delete(calendarId='primary', eventId=gcal_id) return service.events().delete(calendarId="primary", eventId=gcal_id)
except GCalMapping.DoesNotExist:
logger.warning(f"No GCal mapping found for event {event.id}")
return None
# ------------------------------------- Synchronization ------------------------------------------------------------- # ------------------------------------- Synchronization ----------------------------------------------------
def delete_all_gcal_events(service=None): def delete_all_gcal_events(service=None):
"""Deletes all gcal events that have been created by this script""" """Deletes all gcal events that have been created by this script."""
if service is None: if service is None:
service = get_service_object() service = get_service_object()
gcal_ids = [ev['id'] for ev in get_all_gcal_events(service)] if service is None:
num_ids = len(gcal_ids) logger.error("No service object available")
if num_ids == 0: return 0
return num_ids
gcal_events = get_all_gcal_events(service)
gcal_ids = [ev["id"] for ev in gcal_events]
count = len(gcal_ids)
if count == 0:
return 0
# Use batch request for efficiency
from googleapiclient.http import BatchHttpRequest
batch = BatchHttpRequest() batch = BatchHttpRequest()
for ev_id in gcal_ids: for gcal_id in gcal_ids:
batch.add(service.events().delete(calendarId='primary', eventId=ev_id)) batch.add(service.events().delete(calendarId="primary", eventId=gcal_id))
try:
batch.execute() batch.execute()
except Exception as e:
logger.error(f"Error deleting GCal events: {e}")
GCalMapping.objects.all().delete() GCalMapping.objects.all().delete()
return num_ids return count
def sync_from_local_to_google(service=None): def sync_from_local_to_google(service=None):
""" Creates a google event for each local event (if it does not exist yet) and deletes all google events
that are not found in local database. Updates participation info of gcal events using local data
""" """
Creates a google event for each local event (if it does not exist yet) and
deletes all google events that are not found in local database.
Updates participation info of gcal events using local data.
"""
if service is None: if service is None:
service = get_service_object() service = get_service_object()
if service is None:
logger.error("No service object available for sync")
return 0, 0
all_events = get_all_gcal_events(service) all_events = get_all_gcal_events(service)
events_at_google_django_id = set() events_at_google_django_id = set()
events_at_google_google_id = set() events_at_google_google_id = set()
for gcal_ev in all_events:
events_at_google_django_id.add(int(gcal_ev['extendedProperties']['private']['blechreizID']))
events_at_google_google_id.add(gcal_ev['id'])
local_events_django_id = set(Event.objects.all().values_list('pk', flat=True)) for gcal_ev in all_events:
local_events_google_id = set(GCalMapping.objects.all().values_list('gcal_id', flat=True)) try:
django_id = int(gcal_ev["extendedProperties"]["private"]["blechreizID"])
events_at_google_django_id.add(django_id)
events_at_google_google_id.add(gcal_ev["id"])
except (KeyError, ValueError) as e:
logger.warning(f"Invalid GCal event structure: {e}")
local_events_django_id = set(Event.objects.all().values_list("pk", flat=True))
local_events_google_id = set(
GCalMapping.objects.all().values_list("gcal_id", flat=True)
)
events_to_create_django_id = local_events_django_id - events_at_google_django_id events_to_create_django_id = local_events_django_id - events_at_google_django_id
events_to_delete_google_id = events_at_google_google_id - local_events_google_id events_to_delete_google_id = events_at_google_google_id - local_events_google_id
from googleapiclient.http import BatchHttpRequest
batch = BatchHttpRequest() batch = BatchHttpRequest()
batch_is_empty = True batch_is_empty = True
for event_django_id in events_to_create_django_id: for event_django_id in events_to_create_django_id:
batch.add(create_gcal_event(service, Event.objects.get(pk=event_django_id)), callback=on_gcal_event_created)
batch_is_empty = False
for eventGoogleID in events_to_delete_google_id:
batch.add(service.events().delete(calendarId='primary', eventId=eventGoogleID))
batch_is_empty = False
for gcal_ev in all_events:
event_django_id = int(gcal_ev['extendedProperties']['private']['blechreizID'])
try: try:
django_ev = Event.objects.get(pk=event_django_id) event = Event.objects.get(pk=event_django_id)
if 'attendees' not in gcal_ev: batch.add(
gcal_ev['attendees'] = [] create_gcal_event_request(service, event),
callback=on_gcal_event_created,
if gcal_ev['attendees'] != build_gcal_attendees_obj(django_ev): )
batch.add(update_gcal_event(service, django_ev))
batch_is_empty = False batch_is_empty = False
except Event.DoesNotExist: except Event.DoesNotExist:
pass pass
for event_google_id in events_to_delete_google_id:
batch.add(
service.events().delete(calendarId="primary", eventId=event_google_id)
)
batch_is_empty = False
for gcal_ev in all_events:
try:
event_django_id = int(
gcal_ev["extendedProperties"]["private"]["blechreizID"]
)
django_ev = Event.objects.get(pk=event_django_id)
gcal_attendees = gcal_ev.get("attendees", [])
local_attendees = build_gcal_attendees_obj(django_ev)
# Simple comparison - check if attendees differ
if gcal_attendees != local_attendees:
batch.add(update_gcal_event_request(service, django_ev))
batch_is_empty = False
except Event.DoesNotExist:
pass
except (KeyError, ValueError):
pass
if not batch_is_empty: if not batch_is_empty:
try:
batch.execute() batch.execute()
except Exception as e:
logger.error(f"Error executing batch request: {e}")
return len(events_to_create_django_id), len(events_to_delete_google_id) return len(events_to_create_django_id), len(events_to_delete_google_id)
def sync_from_google_to_local(service=None): def sync_from_google_to_local(service=None):
"""Retrieves only participation infos for all events and updates local database if anything has changed. """ """
Retrieves only participation infos for all events and
updates local database if anything has changed.
"""
if service is None: if service is None:
service = get_service_object() service = get_service_object()
if service is None:
logger.error("No service object available for sync")
return False
new_status_received = False new_status_received = False
all_events = get_all_gcal_events(service, from_now=True) all_events = get_all_gcal_events(service, from_now=True)
for e in all_events: for e in all_events:
local_id = e['extendedProperties']['private']['blechreizID'] try:
local_id = e["extendedProperties"]["private"]["blechreizID"]
local_event = Event.objects.get(pk=local_id) local_event = Event.objects.get(pk=local_id)
for a in e['attendees']:
user = UserGCalCoupling.objects.get(email=a['email']).user for a in e.get("attendees", []):
try:
user_coupling = UserGCalCoupling.objects.get(email=a["email"])
user = user_coupling.user
part = EventParticipation.get_or_create(user, local_event) part = EventParticipation.get_or_create(user, local_event)
if 'comment' in a:
part.comment = a['comment']
if a['responseStatus'] == 'needsAction': if "comment" in a:
part.comment = a["comment"]
response_status = a.get("responseStatus", "needsAction")
if response_status == "needsAction":
part.status = "-" part.status = "-"
elif a['responseStatus'] == 'tentative': elif response_status == "tentative":
part.status = '?' part.status = "?"
elif a['responseStatus'] == 'accepted': elif response_status == "accepted":
part.status = 'Yes' part.status = "Yes"
elif a['responseStatus'] == 'declined': elif response_status == "declined":
part.status = 'No' part.status = "No"
else: else:
logger.error("Unknown response status when mapping gcal event: " + a['responseStatus']) logger.error(
f"Unknown response status when mapping gcal event: {response_status}"
)
prev = EventParticipation.objects.get(event=part.event, user=part.user) prev = EventParticipation.objects.get(
event=part.event, user=part.user
)
# Important: Save only if the participation info has changed # Important: Save only if the participation info has changed
# otherwise everything is synced back to google via the post save signal # otherwise everything is synced back to google via the post save signal
@@ -314,15 +447,28 @@ def sync_from_google_to_local(service=None):
part.save() part.save()
new_status_received = True new_status_received = True
except UserGCalCoupling.DoesNotExist:
pass
except Event.DoesNotExist:
logger.warning(f"Event with id {local_id} not found in local database")
except KeyError as e:
logger.warning(f"Invalid event structure: {e}")
return new_status_received return new_status_received
# ------------------------------------- Synchronization ---------------------------------------------------- # ------------------------------------- Push Channel Management ----------------------------------------------------
def check_gcal_subscription(service=None, time_to_live=14 * 24 * 3600, renew_before_expiry=None): def check_gcal_subscription(
"""Google offers a push service if any event information has changed. This works using a so called service=None, time_to_live=14 * 24 * 3600, renew_before_expiry=None
channel, which has a certain time to live. This method checks that a valid channel exists: ):
"""
Google offers a push service if any event information has changed.
This works using a so called channel, which has a certain time to live.
This method checks that a valid channel exists:
- if none exists a new one is created - if none exists a new one is created
- if existing channel does expire soon, the channel is renewed - if existing channel does expire soon, the channel is renewed
- if channel has already expired a sync is triggered and a new channel is created - if channel has already expired a sync is triggered and a new channel is created
@@ -330,37 +476,40 @@ def check_gcal_subscription(service=None, time_to_live=14 * 24 * 3600, renew_bef
if service is None: if service is None:
service = get_service_object() service = get_service_object()
if service is None:
logger.error("No service object available")
return
if renew_before_expiry is None: if renew_before_expiry is None:
renew_before_expiry = 0.8 * time_to_live renew_before_expiry = 0.8 * time_to_live
callback_url = settings.GCAL_COUPLING['push_url'] callback_url = settings.GCAL_COUPLING["push_url"]
# Test if a channel already exists for this callbackURL
try: try:
db_channel = GCalPushChannel.objects.get(address=callback_url) db_channel = GCalPushChannel.objects.get(address=callback_url)
g_channel = db_channel.to_google_channel()
# if expiration time between 0 and two days: stop and create new channel
cur_time = int(time.time() * 1000) cur_time = int(time.time() * 1000)
if g_channel.expiration > cur_time:
if db_channel.expiration > cur_time:
# not yet expired # not yet expired
if cur_time + renew_before_expiry * 1000 > g_channel.expiration: if cur_time + renew_before_expiry * 1000 > db_channel.expiration:
# will expire in less than "renewBeforeExpiry" # will expire in less than "renew_before_expiry"
logger.info("Renewing Google Calendar Subscription: " + callback_url) logger.info(f"Renewing Google Calendar Subscription: {callback_url}")
GCalPushChannel.stop(service, g_channel) db_channel.stop(service)
GCalPushChannel.create_new(callback_url, service, time_to_live) GCalPushChannel.create_new(callback_url, service, time_to_live)
else: else:
logger.info("Channel active until %d " % (g_channel.expiration,)) logger.info(f"Channel active until {db_channel.expiration}")
else: else:
logger.info("Google calendar subscription had expired - getting new subscription") logger.info(
"Google calendar subscription had expired - getting new subscription"
)
# to get back in sync again we have to decide which data to take # to get back in sync again we have to decide which data to take
# so we use the local data as reference # so we use the local data as reference
sync_from_local_to_google(service) sync_from_local_to_google(service)
GCalPushChannel.create_new(callback_url, service, time_to_live) GCalPushChannel.create_new(callback_url, service, time_to_live)
except GCalPushChannel.DoesNotExist: except GCalPushChannel.DoesNotExist:
# create new channel and save it in database logger.info(f"No GCalCallback Channel exists yet for: {callback_url}")
logger.info("No CGalCallback Channel exists yet for: " + callback_url)
# to get back in sync again we have to decide which data to take # to get back in sync again we have to decide which data to take
# so we use the local data as reference # so we use the local data as reference
sync_from_local_to_google(service) sync_from_local_to_google(service)
@@ -368,45 +517,68 @@ def check_gcal_subscription(service=None, time_to_live=14 * 24 * 3600, renew_bef
def stop_all_gcal_subscriptions(service=None): def stop_all_gcal_subscriptions(service=None):
"""Stops the channel subscription """ """Stops all channel subscriptions."""
if service is None: if service is None:
service = get_service_object() service = get_service_object()
for dbChannel in GCalPushChannel.objects.all(): if service is None:
logger.info("Stopping %s expiry at %d " % (dbChannel.id, dbChannel.expiration)) logger.error("No service object available")
GCalPushChannel.stop(service, dbChannel.to_google_channel()) return
for db_channel in GCalPushChannel.objects.all():
logger.info(
f"Stopping channel {db_channel.id} expiry at {db_channel.expiration}"
)
db_channel.stop(service)
def check_if_google_callback_is_valid(token, channel_id, resource_id, service=None): def check_if_google_callback_is_valid(token, channel_id, resource_id, service=None):
"""Validate an incoming Google Calendar push notification."""
if service is None: if service is None:
service = get_service_object() service = get_service_object()
all_channels = GCalPushChannel.objects.all() all_channels = GCalPushChannel.objects.all()
if len(all_channels) == 0: if len(all_channels) == 0:
return False # no known subscriptions -> callback has to be from an old channel return False # no known subscriptions -> callback has to be from an old channel
if len(all_channels) > 1: if len(all_channels) > 1:
logger.warning("Multiple GCal subscriptions! This is strange and probably an error. " logger.warning(
"All channels are closed and one new is created. ") "Multiple GCal subscriptions! This is strange and probably an error. "
"All channels are closed and one new is created."
)
stop_all_gcal_subscriptions(service) stop_all_gcal_subscriptions(service)
check_gcal_subscription() check_gcal_subscription()
all_channels = GCalPushChannel.objects.all() all_channels = GCalPushChannel.objects.all()
assert (len(all_channels) == 1) if len(all_channels) != 1:
return False
the_channel = all_channels[0] the_channel = all_channels[0]
if channel_id != the_channel.id or resource_id != the_channel.resource_id or token != the_channel.token: if (
logger.warning("Got GCal Response from an unexpected Channel" channel_id != the_channel.id
"Got (%s,%s,%s) " or resource_id != the_channel.resource_id
"expected (%s,%s,%s) " or token != the_channel.token
"Old Channel is stopped." ):
% (channel_id, resource_id, token, the_channel.id, the_channel.resource_id, the_channel.token)) logger.warning(
f"Got GCal Response from an unexpected Channel. "
channel_to_stop = GCalPushChannel(id=channel_id, resource_id=resource_id, token=token) f"Got ({channel_id}, {resource_id}, {token}) "
GCalPushChannel.stop(service, channel_to_stop.to_google_channel()) f"expected ({the_channel.id}, {the_channel.resource_id}, {the_channel.token}). "
f"Old Channel is stopped."
)
GCalPushChannel.stop_channel(service, channel_id, resource_id)
return False return False
return True return True
# Backwards compatibility aliases
syncFromLocalToGoogle = sync_from_local_to_google
syncFromGoogleToLocal = sync_from_google_to_local
checkIfGoogleCallbackIsValid = check_if_google_callback_is_valid
checkGCalSubscription = check_gcal_subscription
stopAllGCalSubscriptions = stop_all_gcal_subscriptions
deleteAllGCalEvents = delete_all_gcal_events
getServiceObject = get_service_object

View File

@@ -1,11 +1,9 @@
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from eventplanner_gcal.google_sync import check_gcal_subscription from eventplanner_gcal.google_sync import checkGCalSubscription
class Command(NoArgsCommand): class Command(NoArgsCommand):
help = 'Checks if the GCal notification channel is still active' help = 'Checks if the GCal notification channel is still active'
def handle_noargs(self, **options): def handle_noargs(self, **options):
print ( "Checking Subscription") print ( "Checking Subscription")
check_gcal_subscription() checkGCalSubscription()

View File

@@ -1,11 +1,9 @@
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from eventplanner_gcal.google_sync import delete_all_gcal_events from eventplanner_gcal.google_sync import deleteAllGCalEvents
class Command(NoArgsCommand): class Command(NoArgsCommand):
help = 'Delete all events in the google calendar created by this app' help = 'Delete all events in the google calendar created by this app'
def handle_noargs(self, **options): def handle_noargs(self, **options):
print ("Deleting all GCal Events.") print ("Deleting all GCal Events.")
nr_of_deleted_events = delete_all_gcal_events() nrOfDeletedEvents = deleteAllGCalEvents()
print("Deleted %d events. To Restore them from local database run gcal_sync" % (nr_of_deleted_events,)) print ("Deleted %d events. To Restore them from local database run gcal_sync" % (nrOfDeletedEvents, ) )

View File

@@ -1,10 +1,8 @@
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from eventplanner_gcal.google_sync import stop_all_gcal_subscriptions from eventplanner_gcal.google_sync import stopAllGCalSubscriptions
class Command(NoArgsCommand): class Command(NoArgsCommand):
help = 'Stops all GCal subscriptions' help = 'Stops all GCal subscriptions'
def handle_noargs(self, **options): def handle_noargs(self, **options):
stop_all_gcal_subscriptions() stopAllGCalSubscriptions()

View File

@@ -1,11 +1,10 @@
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from eventplanner_gcal.google_sync import sync_from_local_to_google
from eventplanner_gcal.google_sync import syncFromLocalToGoogle
class Command(NoArgsCommand): class Command(NoArgsCommand):
help = 'Synchronize Google Calendar with locally stored Events' help = 'Synchronize Google Calendar with locally stored Events'
def handle_noargs(self, **options): def handle_noargs(self, **options):
print ( "Running Sync") print ( "Running Sync")
created, deleted = sync_from_local_to_google() created, deleted = syncFromLocalToGoogle()
print ( "Created %d and deleted %d events" % (created,deleted) ) print ( "Created %d and deleted %d events" % (created,deleted) )

View File

@@ -0,0 +1,55 @@
# Generated by Django 5.1.15 on 2026-03-30 19:15
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('eventplanner', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='GCalMapping',
fields=[
('gcal_id', models.CharField(max_length=64)),
('event', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='eventplanner.event')),
],
options={
'verbose_name': 'Google Calendar Mapping',
'verbose_name_plural': 'Google Calendar Mappings',
},
),
migrations.CreateModel(
name='GCalPushChannel',
fields=[
('id', models.CharField(max_length=128, primary_key=True, serialize=False)),
('address', models.CharField(max_length=256)),
('token', models.CharField(max_length=128)),
('resource_id', models.CharField(max_length=128)),
('expiration', models.BigIntegerField()),
],
options={
'verbose_name': 'Google Calendar Push Channel',
'verbose_name_plural': 'Google Calendar Push Channels',
},
),
migrations.CreateModel(
name='UserGCalCoupling',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.CharField(max_length=1024)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'User Google Calendar Coupling',
'verbose_name_plural': 'User Google Calendar Couplings',
},
),
]

View File

View File

@@ -1,59 +1,127 @@
import logging import logging
import uuid import uuid
from eventplanner.models import Event
from django.contrib.auth.models import User
from apiclient.channel import Channel from django.contrib.auth.models import User
from django.db import models from django.db import models
from eventplanner.models import Event
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class UserGCalCoupling(models.Model): class UserGCalCoupling(models.Model):
# For every user in this table the gcal coupling is activated """For every user in this table the gcal coupling is activated."""
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
email = models.CharField(max_length=1024) email = models.CharField(max_length=1024)
class Meta:
verbose_name = "User Google Calendar Coupling"
verbose_name_plural = "User Google Calendar Couplings"
def __str__(self):
return f"{self.user.username} <-> {self.email}"
class GCalMapping(models.Model): class GCalMapping(models.Model):
"""Mapping between event id at google and local event id""" """Mapping between event id at google and local event id."""
gcal_id = models.CharField(max_length=64) gcal_id = models.CharField(max_length=64)
event = models.OneToOneField(Event, primary_key=True, on_delete=models.CASCADE) event = models.OneToOneField(Event, on_delete=models.CASCADE, primary_key=True)
class Meta:
verbose_name = "Google Calendar Mapping"
verbose_name_plural = "Google Calendar Mappings"
def __str__(self):
return f"GCal:{self.gcal_id} <-> Event:{self.event_id}"
class GCalPushChannel(models.Model): class GCalPushChannel(models.Model):
"""This table has either zero or one entry. Required to store if a channel already exists, """This table has either zero or one entry. Required to store if a channel already exists,
when it expires and how to stop (renew) the channel when it expires and how to stop (renew) the channel.
""" """
id = models.CharField(max_length=128, primary_key=True) id = models.CharField(max_length=128, primary_key=True)
address = models.CharField(max_length=256) address = models.CharField(max_length=256)
token = models.CharField(max_length=128) token = models.CharField(max_length=128)
resource_id = models.CharField(max_length=128) resource_id = models.CharField(max_length=128)
expiration = models.IntegerField() expiration = models.BigIntegerField()
def to_google_channel(self): class Meta:
return Channel('web_hook', self.id, self.token, self.address, self.expiration, resource_id=self.resource_id) verbose_name = "Google Calendar Push Channel"
verbose_name_plural = "Google Calendar Push Channels"
@staticmethod def __str__(self):
def from_google_channel(google_channel): return f"Channel {self.id} expires at {self.expiration}"
return GCalPushChannel(id=google_channel.id,
address=google_channel.address,
token=google_channel.token,
expiration=google_channel.expiration,
resource_id=google_channel.resource_id)
@staticmethod def to_channel_dict(self):
def create_new(callback_url, service, ttl=None): """Return a dictionary representation for the Google API."""
gChannel = Channel('web_hook', str(uuid.uuid4()), 'blechreizGcal', callback_url, params={'ttl': int(ttl)}) return {
response = service.events().watch(calendarId='primary', body=gChannel.body()).execute() "id": self.id,
gChannel.update(response) "type": "web_hook",
"address": self.address,
"token": self.token,
"expiration": self.expiration,
"resourceId": self.resource_id,
}
dbChannel = GCalPushChannel.from_google_channel(gChannel) @classmethod
dbChannel.save() def from_response(cls, response, address, token):
"""Create a GCalPushChannel from a Google API response."""
return cls(
id=response["id"],
address=address,
token=token,
expiration=int(response.get("expiration", 0)),
resource_id=response.get("resourceId", ""),
)
@staticmethod @classmethod
def stop(service, google_channel): def create_new(cls, callback_url, service, ttl=None):
channel_service = service.channels() """Create a new push channel subscription."""
channel_service.stop(body=google_channel.body()).execute() channel_id = str(uuid.uuid4())
GCalPushChannel.from_google_channel(google_channel).delete() token = "blechreizGcal"
body = {
"id": channel_id,
"type": "web_hook",
"address": callback_url,
"token": token,
}
if ttl:
body["params"] = {"ttl": str(int(ttl))}
response = service.events().watch(calendarId="primary", body=body).execute()
db_channel = cls.from_response(response, callback_url, token)
db_channel.save()
return db_channel
def stop(self, service):
"""Stop this push channel subscription."""
body = {
"id": self.id,
"resourceId": self.resource_id,
}
try:
service.channels().stop(body=body).execute()
except Exception as e:
logger.warning(f"Failed to stop channel {self.id}: {e}")
self.delete()
@classmethod
def stop_channel(cls, service, channel_id, resource_id):
"""Stop a push channel by ID and resource ID."""
body = {
"id": channel_id,
"resourceId": resource_id,
}
try:
service.channels().stop(body=body).execute()
except Exception as e:
logger.warning(f"Failed to stop channel {channel_id}: {e}")
# Also delete from database if it exists
cls.objects.filter(id=channel_id).delete()

View File

@@ -1,61 +1,120 @@
from django.db.models.signals import post_save, pre_delete """
from django.dispatch import receiver Django signal handlers for Google Calendar synchronization.
from eventplanner.models import Event, EventParticipation
from eventplanner_gcal.google_sync import get_service_object, \ These handlers are currently disabled (early return) but can be enabled
create_gcal_event, delete_gcal_event, update_gcal_event, on_gcal_event_created to automatically sync events with Google Calendar when they are created,
updated, or deleted.
"""
import logging import logging
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from eventplanner.models import Event, EventParticipation
from .google_sync import (
create_gcal_event_request,
delete_gcal_event_request,
get_service_object,
on_gcal_event_created,
update_gcal_event_request,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# @receiver(post_save, sender=User) # @receiver(post_save, sender=User)
# def user_changed(**kwargs): # def user_changed(**kwargs):
# """Sync with Google when user information changes."""
# logger.info("Synchronizing with google - user information changed") # logger.info("Synchronizing with google - user information changed")
# syncFromLocalToGoogle( getServiceObject() ) # sync_from_local_to_google(get_service_object())
@receiver(post_save, sender=Event) @receiver(post_save, sender=Event)
def event_post_save_handler(**kwargs): def event_post_save_handler(sender, instance, created, **kwargs):
if not settings.GCAL_SYNC_ENABLED: """
Handle Event post_save signal.
Creates or updates the corresponding Google Calendar event.
Currently disabled - remove the early return to enable.
"""
# Disabled - remove this return statement to enable auto-sync
return
event = instance
service = get_service_object()
if service is None:
logger.warning("No Google Calendar service available")
return return
event = kwargs['instance']
created = kwargs['created']
try: try:
if created: if created:
logger.info("Creating Gcal event") logger.info(f"Creating GCal event for Event {event.id}")
response = create_gcal_event(get_service_object(), event).execute() request = create_gcal_event_request(service, event)
response = request.execute()
on_gcal_event_created(None, response, None) on_gcal_event_created(None, response, None)
else: else:
logger.info("Updating Gcal event") logger.info(f"Updating GCal event for Event {event.id}")
update_gcal_event(get_service_object(), event).execute() request = update_gcal_event_request(service, event)
request.execute()
except Exception as e: except Exception as e:
logger.error("Error updating Gcal event" + str(e)) logger.error(f"Error syncing event {event.id} to GCal: {e}")
@receiver(pre_delete, sender=Event) @receiver(pre_delete, sender=Event)
def event_pre_delete_handler(**kwargs): def event_pre_delete_handler(sender, instance, **kwargs):
if not settings.GCAL_SYNC_ENABLED: """
Handle Event pre_delete signal.
Deletes the corresponding Google Calendar event.
Currently disabled - remove the early return to enable.
"""
# Disabled - remove this return statement to enable auto-sync
return
event = instance
service = get_service_object()
if service is None:
logger.warning("No Google Calendar service available")
return return
try: try:
event = kwargs['instance'] logger.info(f"Deleting GCal event for Event {event.id}")
logger.info("Deleting Gcal event") request = delete_gcal_event_request(service, event)
delete_gcal_event(get_service_object(), event).execute() if request:
request.execute()
except Exception as e: except Exception as e:
logger.error("Error deleting GCAL event" + str(e)) logger.error(f"Error deleting GCal event for Event {event.id}: {e}")
@receiver(post_save, sender=EventParticipation) @receiver(post_save, sender=EventParticipation)
def participation_post_save_handler(**kwargs): def participation_post_save_handler(sender, instance, **kwargs):
if not settings.GCAL_SYNC_ENABLED: """
Handle EventParticipation post_save signal.
Updates the Google Calendar event when participation changes.
Currently disabled - remove the early return to enable.
"""
# Disabled - remove this return statement to enable auto-sync
return
participation = instance
service = get_service_object()
if service is None:
logger.warning("No Google Calendar service available")
return return
try: try:
participation = kwargs['instance'] logger.info(
logger.info("Participation post save -> update gcal") f"Participation changed for Event {participation.event.id} "
update_gcal_event(get_service_object(), participation.event).execute() f"by User {participation.user.username}"
)
request = update_gcal_event_request(service, participation.event)
request.execute()
except Exception as e: except Exception as e:
logger.error("Error deleting GCAL event" + str(e)) logger.error(f"Error updating GCal event for participation change: {e}")

View File

@@ -1,10 +1,10 @@
{% extends "website/base.html" %} {% extends "website/base.html" %}
{% load sekizai_tags staticfiles %} {% load sekizai_tags static %}
{% block content %} {% block content %}
{% addtoblock "css" strip %} {% addtoblock "css" strip %}
<link rel="stylesheet" href="{{STATIC_URL}}css/bootstrap-switch.min.css" type="text/css" media="screen" /> <link rel="stylesheet" href="{{STATIC_URL}}/css/bootstrap-switch.min.css" type="text/css" media="screen" />
{% endaddtoblock %} {% endaddtoblock %}
{% addtoblock "css" %} {% addtoblock "css" %}
@@ -25,7 +25,7 @@
{% endaddtoblock %} {% endaddtoblock %}
{% addtoblock "js" strip %} {% addtoblock "js" strip %}
<script src="{{STATIC_URL}}js/bootstrap-switch.min.js"></script> <script src="{{STATIC_URL}}/js/bootstrap-switch.min.js"></script>
{% endaddtoblock %} {% endaddtoblock %}
{% addtoblock "js" %} {% addtoblock "js" %}
@@ -72,7 +72,7 @@
weil man alle anderen eigenen Termine auch im Blick hat. weil man alle anderen eigenen Termine auch im Blick hat.
</p> </p>
<img src="{{STATIC_URL}}img/screenshot.png"> <img src="{{STATIC_URL}}/img/screenshot.png">
<p> <p>
<h5>SO GEHTS:</h5> <h5>SO GEHTS:</h5>
@@ -115,7 +115,7 @@
</div> </div>
<div class="span3 offset1"> <div class="span3 offset1">
<img src="{{STATIC_URL}}img/google_cal.png"> <img src="{{STATIC_URL}}/img/google_cal.png">
</div> </div>

View File

@@ -1,9 +1,11 @@
from django.conf.urls import url from django.urls import path
from .views import run_sync, gcal_api_callback, manage from . import views
app_name = "eventplanner_gcal"
urlpatterns = [ urlpatterns = [
url(r'^runSync$', run_sync), path("runSync/", views.run_sync, name="run_sync"),
url(r'^gcalApiCallback$', gcal_api_callback), path("gcalApiCallback/", views.gcal_api_callback, name="gcal_api_callback"),
url(r'^manage$', manage), path("manage/", views.manage, name="manage"),
] ]

View File

@@ -1,63 +1,101 @@
from django.shortcuts import redirect """
from eventplanner_gcal.google_sync import sync_from_google_to_local, sync_from_local_to_google Views for Google Calendar integration management.
from django.http import HttpResponse """
from django.views.decorators.csrf import csrf_exempt
from eventplanner_gcal.google_sync import check_if_google_callback_is_valid
from eventplanner_gcal.models import UserGCalCoupling
from django.shortcuts import render
import logging import logging
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.views.decorators.csrf import csrf_exempt
from .google_sync import (
check_if_google_callback_is_valid,
sync_from_google_to_local,
sync_from_local_to_google,
)
from .models import UserGCalCoupling
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def run_sync(request): def run_sync(request):
"""Manually trigger a sync from local to Google Calendar."""
sync_from_local_to_google() sync_from_local_to_google()
return redirect("/") return redirect("/")
def manage(request): def manage(request):
if request.method == 'POST': """
if request.POST['activate'] == "1": View for managing Google Calendar integration settings.
Allows users to enable/disable GCal sync and configure their email.
"""
if request.method == "POST":
activate = request.POST.get("activate", "0")
if activate == "1":
# Enable GCal coupling
email = request.POST.get("email", "")
if email:
UserGCalCoupling.objects.filter(user=request.user).delete() UserGCalCoupling.objects.filter(user=request.user).delete()
c = UserGCalCoupling(user=request.user, email=request.POST['email']) coupling = UserGCalCoupling(user=request.user, email=email)
c.save() coupling.save()
sync_from_local_to_google() sync_from_local_to_google()
else: else:
# Disable GCal coupling
UserGCalCoupling.objects.filter(user=request.user).delete() UserGCalCoupling.objects.filter(user=request.user).delete()
sync_from_local_to_google() sync_from_local_to_google()
context = {} context = {}
user_coupling = UserGCalCoupling.objects.filter(user=request.user) user_coupling = UserGCalCoupling.objects.filter(user=request.user)
context['enabled'] = len(user_coupling) context["enabled"] = user_coupling.exists()
assert (len(user_coupling) < 2)
if len(user_coupling) == 1:
context['mail'] = user_coupling[0].email
return render(request, 'eventplanner_gcal/management.html', context) if user_coupling.count() > 1:
logger.warning(
f"User {request.user.username} has multiple GCal couplings. "
"This should not happen."
)
if user_coupling.exists():
context["mail"] = user_coupling.first().email
return render(request, "eventplanner_gcal/management.html", context)
@csrf_exempt @csrf_exempt
def gcal_api_callback(request): def gcal_api_callback(request):
token = "" """
if 'HTTP_X_GOOG_CHANNEL_TOKEN' in request.META: Callback endpoint for Google Calendar push notifications.
token = request.META['HTTP_X_GOOG_CHANNEL_TOKEN']
channel_id = "" This is called by Google when calendar events are updated.
if 'HTTP_X_GOOG_CHANNEL_ID' in request.META: """
channel_id = request.META['HTTP_X_GOOG_CHANNEL_ID'] token = request.META.get("HTTP_X_GOOG_CHANNEL_TOKEN", "")
channel_id = request.META.get("HTTP_X_GOOG_CHANNEL_ID", "")
resource_id = "" resource_id = request.META.get("HTTP_X_GOOG_RESOURCE_ID", "")
if 'HTTP_X_GOOG_RESOURCE_ID' in request.META:
resource_id = request.META['HTTP_X_GOOG_RESOURCE_ID']
valid = check_if_google_callback_is_valid(token, channel_id, resource_id) valid = check_if_google_callback_is_valid(token, channel_id, resource_id)
if not valid: if not valid:
return HttpResponse('<h1>Old Channel - no update triggered</h1>') logger.warning(
f"Received invalid GCal callback: token={token}, "
f"channel_id={channel_id}, resource_id={resource_id}"
)
return HttpResponse("<h1>Old Channel - no update triggered</h1>")
logger.info(
f"Received Google Callback with headers - "
f"Token: {token}, ID: {channel_id}, ResID: {resource_id}"
)
logger.info("Received Google Callback with the following headers Token: "
"%s ID %s ResID %s " % (token, channel_id, resource_id))
result = sync_from_google_to_local() result = sync_from_google_to_local()
logger.info("Finished processing callback from GCal - New Information present: %d " % (result,))
return HttpResponse('<h1>Callback successful</h1>') logger.info(
f"Finished processing callback from GCal - New Information present: {result}"
)
return HttpResponse("<h1>Callback successful</h1>")
# Backwards compatibility aliases
runSync = run_sync
gcalApiCallback = gcal_api_callback

0
imagestore/__init__.py Normal file
View File

36
imagestore/admin.py Normal file
View File

@@ -0,0 +1,36 @@
from django.contrib import admin
from imagestore.models import Image, Album, AlbumUpload
from sorl.thumbnail.admin import AdminInlineImageMixin
from django.conf import settings
class InlineImageAdmin(AdminInlineImageMixin, admin.TabularInline):
model = Image
fieldsets = ((None, {'fields': ['image', 'user', 'title', 'order', 'tags', 'album']}),)
raw_id_fields = ('user', )
extra = 0
class AlbumAdmin(admin.ModelAdmin):
fieldsets = ((None, {'fields': ['name', 'user', 'is_public', 'order']}),)
list_display = ('name', 'admin_thumbnail', 'user', 'created', 'updated', 'is_public', 'order')
list_editable = ('order', )
inlines = [InlineImageAdmin]
admin.site.register(Album, AlbumAdmin)
class ImageAdmin(admin.ModelAdmin):
fieldsets = ((None, {'fields': ['user', 'title', 'image', 'description', 'order', 'tags', 'album']}),)
list_display = ('admin_thumbnail', 'user', 'order', 'album', 'title')
raw_id_fields = ('user', )
list_filter = ('album', )
class AlbumUploadAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
return False
IMAGE_MODEL = getattr(settings, 'IMAGESTORE_IMAGE_MODEL', None)
if not IMAGE_MODEL:
admin.site.register(Image, ImageAdmin)
ALBUM_MODEL = getattr(settings, 'IMAGESTORE_ALBUM_MODEL', None)
if not ALBUM_MODEL:
admin.site.register(AlbumUpload, AlbumUploadAdmin)

View File

@@ -0,0 +1,9 @@
# coding=utf-8
from __future__ import unicode_literals
import autocomplete_light
from tagging.models import Tag
autocomplete_light.register(
Tag,
search_fields=['^name']
)

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
from django.core.urlresolvers import reverse, NoReverseMatch
from django.conf import settings
from utils import get_model_string
from imagestore.models import image_applabel, image_classname
from imagestore.models import album_applabel, album_classname
def imagestore_processor(request):
template = getattr(settings, 'IMAGESTORE_TEMPLATE', False)
ret = {
'IMAGESTORE_SHOW_USER': getattr(settings, 'IMAGESTORE_SHOW_USER', True),
'IMAGESTORE_SHOW_TAGS': getattr(settings, 'IMAGESTORE_SHOW_TAGS', True),
'IMAGESTORE_MODEL_STRING': get_model_string('Image'),
'IMAGESTORE_LOAD_CSS': getattr(settings, 'IMAGESTORE_LOAD_CSS', True),
}
try:
ret['imagestore_index_url'] = reverse('imagestore:index')
except NoReverseMatch: #Bastard django-cms from hell!!!!111
pass
if template:
ret['IMAGESTORE_TEMPLATE'] = template
ret['imagestore_perms'] = {
'add_image': request.user.has_perm('%s.add_%s' % (image_applabel, image_classname)),
'add_album': request.user.has_perm('%s.add_%s' % (album_applabel, album_classname)),
}
return ret

42
imagestore/forms.py Normal file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
try:
import autocomplete_light
AUTOCOMPLETE_LIGHT_INSTALLED = True
except ImportError:
AUTOCOMPLETE_LIGHT_INSTALLED = False
__author__ = 'zeus'
from django import forms
from models import Image, Album
from django.utils.translation import ugettext_lazy as _
class ImageForm(forms.ModelForm):
class Meta(object):
model = Image
exclude = ('user', 'order')
description = forms.CharField(widget=forms.Textarea(attrs={'rows': 2, 'cols': 19}), required=False,
label=_('Description'))
def __init__(self, user, *args, **kwargs):
super(ImageForm, self).__init__(*args, **kwargs)
self.fields['album'].queryset = Album.objects.filter(user=user)
self.fields['album'].required = True
if AUTOCOMPLETE_LIGHT_INSTALLED:
self.fields['tags'].widget = autocomplete_light.TextWidget('TagAutocomplete')
class AlbumForm(forms.ModelForm):
class Meta(object):
model = Album
exclude = ('user', 'created', 'updated')
def __init__(self, *args, **kwargs):
super(AlbumForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs and kwargs['instance']:
self.fields['head'].queryset = Image.objects.filter(album=kwargs['instance'])
else:
self.fields['head'].widget = forms.HiddenInput()

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _
class ImagestoreApp(CMSApp):
name = _("Imagestore App") # give your app a name, this is required
urls = ["imagestore.imagestore_cms.urls"] # link your app to url configuration(s)
apphook_pool.register(ImagestoreApp) # register your app

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from models import ImagestoreAlbumPtr, ImagestoreAlbumCarousel
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
class AlbumPlugin(CMSPluginBase):
model = ImagestoreAlbumPtr
name = _('Album')
render_template = "cms/plugins/imagestore_album.html"
text_enabled = True
def render(self, context, instance, placeholder):
context.update({'album': instance.album})
return context
class AlbumCarouselPlugin(CMSPluginBase):
model = ImagestoreAlbumCarousel
name = _('Album as carousel')
render_template = "cms/plugins/imagestore_album_carousel.html"
text_enabled = True
def render(self, context, instance, placeholder):
# default carousel template in the settings file
carousel_template = getattr(settings, 'IMAGESTORE_CAROUSEL_TEMPLATE', None)
if carousel_template:
self.render_template = carousel_template
if instance.template_file:
self.render_template = instance.template_file
else:
if carousel_template:
instance.template_file = carousel_template
else:
instance.template_file = self.render_template
instance.save()
images = instance.album.images.all()
if instance.limit:
images = images[:instance.limit]
context.update({'images': images, 'carousel': instance})
return context
plugin_pool.register_plugin(AlbumCarouselPlugin)
plugin_pool.register_plugin(AlbumPlugin)

View File

@@ -0,0 +1,112 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'ImagestoreAlbumPtr'
db.create_table('cmsplugin_imagestorealbumptr', (
('cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
('album', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['imagestore.Album'])),
))
db.send_create_signal('imagestore_cms', ['ImagestoreAlbumPtr'])
def backwards(self, orm):
# Deleting model 'ImagestoreAlbumPtr'
db.delete_table('cmsplugin_imagestorealbumptr')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cms.cmsplugin': {
'Meta': {'object_name': 'CMSPlugin'},
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'cms.placeholder': {
'Meta': {'object_name': 'Placeholder'},
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore_cms.imagestorealbumptr': {
'Meta': {'object_name': 'ImagestoreAlbumPtr', 'db_table': "'cmsplugin_imagestorealbumptr'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'})
}
}
complete_apps = ['imagestore_cms']

View File

@@ -0,0 +1,121 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'ImagestoreAlbumCarusel'
db.create_table('cmsplugin_imagestorealbumcarusel', (
('cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
('album', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['imagestore.Album'])),
('width', self.gf('django.db.models.fields.IntegerField')(default=200)),
('limit', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
))
db.send_create_signal('imagestore_cms', ['ImagestoreAlbumCarusel'])
def backwards(self, orm):
# Deleting model 'ImagestoreAlbumCarusel'
db.delete_table('cmsplugin_imagestorealbumcarusel')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cms.cmsplugin': {
'Meta': {'object_name': 'CMSPlugin'},
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'cms.placeholder': {
'Meta': {'object_name': 'Placeholder'},
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore_cms.imagestorealbumcarusel': {
'Meta': {'object_name': 'ImagestoreAlbumCarusel', 'db_table': "'cmsplugin_imagestorealbumcarusel'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
'limit': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'width': ('django.db.models.fields.IntegerField', [], {'default': '200'})
},
'imagestore_cms.imagestorealbumptr': {
'Meta': {'object_name': 'ImagestoreAlbumPtr', 'db_table': "'cmsplugin_imagestorealbumptr'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'})
}
}
complete_apps = ['imagestore_cms']

View File

@@ -0,0 +1,137 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting model 'ImagestoreAlbumCarusel'
db.delete_table('cmsplugin_imagestorealbumcarusel')
# Adding model 'ImagestoreAlbumCarousel'
db.create_table('cmsplugin_imagestorealbumcarousel', (
('cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
('album', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['imagestore.Album'])),
('width', self.gf('django.db.models.fields.IntegerField')(default=200)),
('height', self.gf('django.db.models.fields.IntegerField')(default=200)),
('skin', self.gf('django.db.models.fields.CharField')(default='jcarousel-skin-tango', max_length=100)),
('limit', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
))
db.send_create_signal('imagestore_cms', ['ImagestoreAlbumCarousel'])
def backwards(self, orm):
# Adding model 'ImagestoreAlbumCarusel'
db.create_table('cmsplugin_imagestorealbumcarusel', (
('album', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['imagestore.Album'])),
('limit', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
('cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)),
('width', self.gf('django.db.models.fields.IntegerField')(default=200)),
))
db.send_create_signal('imagestore_cms', ['ImagestoreAlbumCarusel'])
# Deleting model 'ImagestoreAlbumCarousel'
db.delete_table('cmsplugin_imagestorealbumcarousel')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cms.cmsplugin': {
'Meta': {'object_name': 'CMSPlugin'},
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'cms.placeholder': {
'Meta': {'object_name': 'Placeholder'},
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore_cms.imagestorealbumcarousel': {
'Meta': {'object_name': 'ImagestoreAlbumCarousel', 'db_table': "'cmsplugin_imagestorealbumcarousel'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
'height': ('django.db.models.fields.IntegerField', [], {'default': '200'}),
'limit': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'skin': ('django.db.models.fields.CharField', [], {'default': "'jcarousel-skin-tango'", 'max_length': '100'}),
'width': ('django.db.models.fields.IntegerField', [], {'default': '200'})
},
'imagestore_cms.imagestorealbumptr': {
'Meta': {'object_name': 'ImagestoreAlbumPtr', 'db_table': "'cmsplugin_imagestorealbumptr'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'})
}
}
complete_apps = ['imagestore_cms']

View File

@@ -0,0 +1,135 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'ImagestoreAlbumCarousel.height'
db.delete_column('cmsplugin_imagestorealbumcarousel', 'height')
# Deleting field 'ImagestoreAlbumCarousel.width'
db.delete_column('cmsplugin_imagestorealbumcarousel', 'width')
# Adding field 'ImagestoreAlbumCarousel.size'
db.add_column('cmsplugin_imagestorealbumcarousel', 'size', self.gf('django.db.models.fields.CharField')(default='72x72', max_length=20), keep_default=False)
# Adding field 'ImagestoreAlbumCarousel.full_size'
db.add_column('cmsplugin_imagestorealbumcarousel', 'full_size', self.gf('django.db.models.fields.CharField')(default='600x600', max_length=20), keep_default=False)
def backwards(self, orm):
# Adding field 'ImagestoreAlbumCarousel.height'
db.add_column('cmsplugin_imagestorealbumcarousel', 'height', self.gf('django.db.models.fields.IntegerField')(default=200), keep_default=False)
# Adding field 'ImagestoreAlbumCarousel.width'
db.add_column('cmsplugin_imagestorealbumcarousel', 'width', self.gf('django.db.models.fields.IntegerField')(default=200), keep_default=False)
# Deleting field 'ImagestoreAlbumCarousel.size'
db.delete_column('cmsplugin_imagestorealbumcarousel', 'size')
# Deleting field 'ImagestoreAlbumCarousel.full_size'
db.delete_column('cmsplugin_imagestorealbumcarousel', 'full_size')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cms.cmsplugin': {
'Meta': {'object_name': 'CMSPlugin'},
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'cms.placeholder': {
'Meta': {'object_name': 'Placeholder'},
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore_cms.imagestorealbumcarousel': {
'Meta': {'object_name': 'ImagestoreAlbumCarousel', 'db_table': "'cmsplugin_imagestorealbumcarousel'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
'full_size': ('django.db.models.fields.CharField', [], {'default': "'600x600'", 'max_length': '20'}),
'limit': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'size': ('django.db.models.fields.CharField', [], {'default': "'72x72'", 'max_length': '20'}),
'skin': ('django.db.models.fields.CharField', [], {'default': "'jcarousel-skin-tango'", 'max_length': '100'})
},
'imagestore_cms.imagestorealbumptr': {
'Meta': {'object_name': 'ImagestoreAlbumPtr', 'db_table': "'cmsplugin_imagestorealbumptr'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'})
}
}
complete_apps = ['imagestore_cms']

View File

@@ -0,0 +1,210 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'ImagestoreAlbumCarousel.template_file'
db.add_column('cmsplugin_imagestorealbumcarousel', 'template_file', self.gf('django.db.models.fields.CharField')(default='cms/plugins/imagestore_album_carousel.html', max_length=100), keep_default=False)
def backwards(self, orm):
# Deleting field 'ImagestoreAlbumCarousel.template_file'
db.delete_column('cmsplugin_imagestorealbumcarousel', 'template_file')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cms.cmsplugin': {
'Meta': {'object_name': 'CMSPlugin'},
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'cms.placeholder': {
'Meta': {'object_name': 'Placeholder'},
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'image.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image', 'db_table': "'imagestore_image'"},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'place': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['places.GeoPlace']"}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['image.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore_cms.imagestorealbumcarousel': {
'Meta': {'object_name': 'ImagestoreAlbumCarousel', 'db_table': "'cmsplugin_imagestorealbumcarousel'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
'full_size': ('django.db.models.fields.CharField', [], {'default': "'600x600'", 'max_length': '20'}),
'limit': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'size': ('django.db.models.fields.CharField', [], {'default': "'72x72'", 'max_length': '20'}),
'skin': ('django.db.models.fields.CharField', [], {'default': "'jcarousel-skin-tango'", 'max_length': '100'}),
'template_file': ('django.db.models.fields.CharField', [], {'default': "'cms/plugins/imagestore_album_carousel.html'", 'max_length': '100'})
},
'imagestore_cms.imagestorealbumptr': {
'Meta': {'object_name': 'ImagestoreAlbumPtr', 'db_table': "'cmsplugin_imagestorealbumptr'", '_ormbases': ['cms.CMSPlugin']},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']"}),
'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'})
},
'places.geoplace': {
'Meta': {'object_name': 'GeoPlace'},
'addional_info': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'imagestore_tag': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'latitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'longtitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'metro': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'minuses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'near_objects': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'near_objects_rel_+'", 'null': 'True', 'to': "orm['places.GeoPlace']"}),
'near_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'path_to': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'phone': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'pluses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'private': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'tags': ('tagging.fields.TagField', [], {}),
'topic': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Topic']", 'null': 'True', 'blank': 'True'}),
'topic_on_demand': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['places.PlaceType']", 'null': 'None', 'blank': 'None'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'work_time': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
},
'places.placetype': {
'Meta': {'object_name': 'PlaceType'},
'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Forum']", 'null': 'True', 'blank': 'True'}),
'forum_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'icon_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'name_plural': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'path_to_image': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
},
'pybb.category': {
'Meta': {'ordering': "['position']", 'object_name': 'Category'},
'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'})
},
'pybb.forum': {
'Meta': {'ordering': "['position']", 'object_name': 'Forum'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'forums'", 'to': "orm['pybb.Category']"}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'headline': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'moderators': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'readed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'readed_forums'", 'symmetrical': 'False', 'through': "orm['pybb.ForumReadTracker']", 'to': "orm['auth.User']"}),
'topic_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'pybb.forumreadtracker': {
'Meta': {'object_name': 'ForumReadTracker'},
'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Forum']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time_stamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'pybb.topic': {
'Meta': {'ordering': "['-created']", 'object_name': 'Topic'},
'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'forum': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'topics'", 'to': "orm['pybb.Forum']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'on_moderation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'readed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'readed_topics'", 'symmetrical': 'False', 'through': "orm['pybb.TopicReadTracker']", 'to': "orm['auth.User']"}),
'sticky': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'subscriptions'", 'blank': 'True', 'to': "orm['auth.User']"}),
'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'views': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'})
},
'pybb.topicreadtracker': {
'Meta': {'object_name': 'TopicReadTracker'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time_stamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'topic': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Topic']", 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore_cms']

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from cms.models import CMSPlugin
from django.db import models
from imagestore.models import Album
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
class ImagestoreAlbumPtr(CMSPlugin):
album = models.ForeignKey(Album, verbose_name=_('Album'), blank=False, null=False)
class ImagestoreAlbumCarousel(CMSPlugin):
album = models.ForeignKey(Album, verbose_name=_('Album'), blank=False, null=False)
skin = models.CharField(max_length=100, verbose_name=_('Skin'), default='jcarousel-skin-tango')
limit = models.IntegerField(verbose_name=_('Image limit'), blank=True, null=True)
size = models.CharField(max_length=20, verbose_name=_('Thumbnail size'), default='72x72')
full_size = models.CharField(max_length=20, verbose_name=_('Full size view'), default='600x600')
template_file = models.CharField(max_length=100, verbose_name=_('Template file'), default=getattr(settings,'IMAGESTORE_CAROUSEL_TEMPLATE','cms/plugins/imagestore_album_carousel.html'), blank=True, null=True)

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from django.conf.urls.defaults import *
from imagestore.views import AlbumListView
urlpatterns = patterns('',
url(r'^', include('imagestore.urls', namespace='imagestore')),
)

Binary file not shown.

View File

@@ -0,0 +1,276 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-09-29 16:54+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: forms.py:22 models/bases/image.py:44
msgid "Description"
msgstr "Beschreibung"
#: views.py:63
#, python-format
msgid "No Tag found matching \"%s\"."
msgstr "Kein Tag gefunden der auf \"%s\" passt."
#: imagestore_cms/cms_app.py:11
msgid "Imagestore App"
msgstr ""
#: imagestore_cms/cms_plugins.py:14 imagestore_cms/models.py:13
#: imagestore_cms/models.py:16 models/album.py:14 models/bases/image.py:51
#: templates/imagestore/image-scope.html:9 templates/imagestore/image.html:16
msgid "Album"
msgstr ""
#: imagestore_cms/cms_plugins.py:25
msgid "Album as carousel"
msgstr "Album als Karousel"
#: imagestore_cms/models.py:17
msgid "Skin"
msgstr "Skin"
#: imagestore_cms/models.py:18
msgid "Image limit"
msgstr "Bilder Limit"
#: imagestore_cms/models.py:19
msgid "Thumbnail size"
msgstr "Thumbnail Größe"
#: imagestore_cms/models.py:20
msgid "Full size view"
msgstr "Vollbild Ansicht"
#: imagestore_cms/models.py:21
msgid "Template file"
msgstr "Template File"
#: models/album.py:15
msgid "Albums"
msgstr "Alben"
#: models/image.py:13 templates/imagestore/image.html:8
msgid "Image"
msgstr "Bild"
#: models/image.py:14
msgid "Images"
msgstr "Bilder"
#: models/upload.py:84
msgid "images file (.zip)"
msgstr "Bild Datei (.zip)"
#: models/upload.py:85
msgid "Select a .zip file of images to upload into a new Gallery."
msgstr "Zip Datei mit Bilder auswählen um eine neue Gallerie zu erstellen."
#: models/upload.py:90
msgid ""
"Select an album to add these images to. leave this empty to create a new "
"album from the supplied title."
msgstr "Bitte Album für neue Bilder auswählen. Leer lassen um ein neues Album zu erstellen."
#: models/upload.py:95
msgid "New album name"
msgstr "Name für neues Album"
#: models/upload.py:96
msgid ""
"If not empty new album with this name will be created and images will be "
"upload to this album"
msgstr "Falls nicht leer wird ein Album mit diesem Namen erstellt, und hochgeladene Bilder werden in dieses Album hinzugefügt."
#: models/upload.py:98
msgid "tags"
msgstr "Tags"
#: models/upload.py:101
msgid "Album upload"
msgstr "Album upload"
#: models/upload.py:102
msgid "Album uploads"
msgstr "Album uploads"
#: models/bases/album.py:38 models/bases/image.py:48
#: templates/imagestore/image-scope.html:4 templates/imagestore/image.html:13
#: templates/imagestore/user_info.html:7
msgid "User"
msgstr "Benutzer"
#: models/bases/album.py:39
msgid "Name"
msgstr "Name"
#: models/bases/album.py:40 models/bases/image.py:49
msgid "Created"
msgstr "Erstellt"
#: models/bases/album.py:41 models/bases/image.py:50
msgid "Updated"
msgstr "Aktualisiert"
#: models/bases/album.py:42
msgid "Is public"
msgstr "Öffentlich"
#: models/bases/album.py:45 models/bases/image.py:46
msgid "Order"
msgstr "Reihenfolge"
#: models/bases/album.py:72 templates/imagestore/album_list.html:53
msgid "Empty album"
msgstr "Leeres Album"
#: models/bases/album.py:74
msgid "Head"
msgstr ""
#: models/bases/image.py:43
msgid "Title"
msgstr "Titel"
#: models/bases/image.py:45 templates/imagestore/image.html:77
#: templates/imagestore/tag-cloud.html:7
msgid "Tags"
msgstr ""
#: models/bases/image.py:47
msgid "File"
msgstr "Datei"
#: models/bases/image.py:68
msgid "Thumbnail"
msgstr "Vorschau"
#: templates/imagestore/album_delete.html:6
msgid "Are you sure that you would like to delete this album?"
msgstr "Dieses Album wirklich löschen?"
#: templates/imagestore/album_delete.html:10
#: templates/imagestore/image_confirm_delete.html:10
#: templates/imagestore/image_delete.html:10
msgid "No, take me back"
msgstr "Nein, zurück"
#: templates/imagestore/album_delete.html:11
#: templates/imagestore/image_confirm_delete.html:11
#: templates/imagestore/image_delete.html:11
msgid "Yes, I am sure"
msgstr "Ja"
#: templates/imagestore/album_list.html:7
#: templates/imagestore/album_list.html:16
#: templates/imagestore/album_list.html:37
msgid "Albums for user"
msgstr ""
#: templates/imagestore/album_list.html:9
#: templates/imagestore/album_list.html:18
#: templates/imagestore/album_list.html:39
msgid "All albums"
msgstr "Alle Alben"
#: templates/imagestore/album_list.html:60
msgid "user"
msgstr "Benutzer"
#: templates/imagestore/base.html:27
msgid "Home"
msgstr "Home"
#: templates/imagestore/base.html:30 templates/imagestore/tag.html:7
msgid "Gallery"
msgstr "Galerie"
#: templates/imagestore/base.html:46
#: templates/imagestore/forms/image_form.html:7
#: templates/imagestore/forms/image_form.html:11
#: templates/imagestore/forms/image_form.html:15
msgid "Upload image"
msgstr "Bild hochladen"
#: templates/imagestore/base.html:49
msgid "Create new album"
msgstr "Neues Album erstellen"
#: templates/imagestore/image-list.html:14
#: templates/imagestore/image_list.html:55
msgid "Info"
msgstr ""
#: templates/imagestore/image-scope.html:14 templates/imagestore/image.html:19
#: templates/imagestore/tag.html:7 templates/imagestore/tag.html.py:11
#: templates/imagestore/tag.html:15
msgid "Tag"
msgstr ""
#: templates/imagestore/image.html:56
msgid "previous image"
msgstr "Vorheriges Bild"
#: templates/imagestore/image.html:59
msgid "next image"
msgstr "Nächstes Bild"
#: templates/imagestore/image.html:69
msgid "Edit info"
msgstr "Info editieren"
#: templates/imagestore/image.html:70
msgid "Delete image"
msgstr "Bild löschen"
#: templates/imagestore/image.html:85
msgid "Place"
msgstr ""
#: templates/imagestore/image_confirm_delete.html:6
#: templates/imagestore/image_delete.html:6
msgid "Are you sure that you would like to delete this image?"
msgstr "Soll das Bild sicher gelöscht werden?"
#: templates/imagestore/image_list.html:34
#: templates/imagestore/forms/album_form.html:13
#: templates/imagestore/forms/album_form.html:23
msgid "Edit album"
msgstr "Album bearbeiten"
#: templates/imagestore/pagination.html:8
msgid "previous page"
msgstr "Vorherige Seite"
#: templates/imagestore/pagination.html:22
msgid "next page"
msgstr "Nächste Seite"
#: templates/imagestore/forms/album_form.html:7
#: templates/imagestore/forms/album_form.html:15
#: templates/imagestore/forms/album_form.html:25
msgid "Create album"
msgstr "Neues album"
#: templates/imagestore/forms/album_form.html:31
msgid "Save"
msgstr "Speichern"
#: templates/imagestore/forms/image_form.html:19
msgid "Upload"
msgstr "Hochladen"

View File

@@ -0,0 +1,78 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Category'
db.create_table('imagestore_category', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['imagestore.Category'])),
('slug', self.gf('django.db.models.fields.SlugField')(max_length=200, db_index=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=200)),
('order', self.gf('django.db.models.fields.IntegerField')()),
('is_public', self.gf('django.db.models.fields.BooleanField')(default=False)),
('lft', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
('rght', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
('tree_id', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
('level', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
))
db.send_create_signal('imagestore', ['Category'])
# Adding model 'Image'
db.create_table('imagestore_image', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=200, null=True, blank=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True)),
('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
('tags', self.gf('tagging.fields.TagField')()),
('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['imagestore.Category'])),
('order', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
('is_public', self.gf('django.db.models.fields.BooleanField')(default=True)),
('image', self.gf('sorl.thumbnail.fields.ImageField')(max_length=100)),
))
db.send_create_signal('imagestore', ['Image'])
def backwards(self, orm):
# Deleting model 'Category'
db.delete_table('imagestore_category')
# Deleting model 'Image'
db.delete_table('imagestore_image')
models = {
'imagestore.category': {
'Meta': {'object_name': 'Category'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['imagestore.Category']"}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'imagestore.image': {
'Meta': {'object_name': 'Image'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Category']"}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '200', 'null': 'True', 'blank': 'True'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,48 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'Image.slug'
db.delete_column('imagestore_image', 'slug')
def backwards(self, orm):
# Adding field 'Image.slug'
db.add_column('imagestore_image', 'slug', self.gf('django.db.models.fields.SlugField')(blank=True, max_length=200, null=True, db_index=True), keep_default=False)
models = {
'imagestore.category': {
'Meta': {'ordering': "('order', 'title')", 'object_name': 'Category'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['imagestore.Category']"}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'imagestore.image': {
'Meta': {'object_name': 'Image'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'images'", 'to': "orm['imagestore.Category']"}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,85 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Image.user'
db.add_column('imagestore_image', 'user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='images', null=True, to=orm['auth.User']), keep_default=False)
def backwards(self, orm):
# Deleting field 'Image.user'
db.delete_column('imagestore_image', 'user_id')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.category': {
'Meta': {'ordering': "('order', 'title')", 'object_name': 'Category'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['imagestore.Category']"}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'imagestore.image': {
'Meta': {'object_name': 'Image'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'images'", 'to': "orm['imagestore.Category']"}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,85 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Image.order'
db.alter_column('imagestore_image', 'order', self.gf('django.db.models.fields.IntegerField')())
def backwards(self, orm):
# Changing field 'Image.order'
db.alter_column('imagestore_image', 'order', self.gf('django.db.models.fields.IntegerField')(null=True))
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.category': {
'Meta': {'ordering': "('order', 'title')", 'object_name': 'Category'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['imagestore.Category']"}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'images'", 'to': "orm['imagestore.Category']"}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,98 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Album'
db.create_table('imagestore_album', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
))
db.send_create_signal('imagestore', ['Album'])
def backwards(self, orm):
# Deleting model 'Album'
db.delete_table('imagestore_album')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'imagestore.category': {
'Meta': {'ordering': "('order', 'title')", 'object_name': 'Category'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['imagestore.Category']"}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'images'", 'to': "orm['imagestore.Category']"}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,106 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Image.created'
db.add_column('imagestore_image', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, blank=True), keep_default=False)
# Adding field 'Image.updated'
db.add_column('imagestore_image', 'updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, null=True, blank=True), keep_default=False)
# Changing field 'Image.category'
db.alter_column('imagestore_image', 'category_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['imagestore.Category']))
def backwards(self, orm):
# Deleting field 'Image.created'
db.delete_column('imagestore_image', 'created')
# Deleting field 'Image.updated'
db.delete_column('imagestore_image', 'updated')
# User chose to not deal with backwards NULL issues for 'Image.category'
raise RuntimeError("Cannot reverse this migration. 'Image.category' and its values cannot be restored.")
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'imagestore.category': {
'Meta': {'ordering': "('order', 'title')", 'object_name': 'Category'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['imagestore.Category']"}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Category']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,131 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting model 'Category'
db.delete_table('imagestore_category')
# Adding field 'Album.user'
db.add_column('imagestore_album', 'user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='albums', null=True, to=orm['auth.User']), keep_default=False)
# Adding field 'Album.is_public'
db.add_column('imagestore_album', 'is_public', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
# Adding field 'Album.head'
db.add_column('imagestore_album', 'head', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='head_of', null=True, to=orm['imagestore.Image']), keep_default=False)
# Deleting field 'Image.is_public'
db.delete_column('imagestore_image', 'is_public')
# Deleting field 'Image.category'
db.delete_column('imagestore_image', 'category_id')
# Adding field 'Image.album'
db.add_column('imagestore_image', 'album', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='images', null=True, to=orm['imagestore.Album']), keep_default=False)
def backwards(self, orm):
# Adding model 'Category'
db.create_table('imagestore_category', (
('rght', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='children', null=True, to=orm['imagestore.Category'], blank=True)),
('lft', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
('is_public', self.gf('django.db.models.fields.BooleanField')(default=False)),
('slug', self.gf('django.db.models.fields.SlugField')(max_length=200, db_index=True)),
('level', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=200)),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('tree_id', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)),
('order', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('imagestore', ['Category'])
# Deleting field 'Album.user'
db.delete_column('imagestore_album', 'user_id')
# Deleting field 'Album.is_public'
db.delete_column('imagestore_album', 'is_public')
# Deleting field 'Album.head'
db.delete_column('imagestore_album', 'head_id')
# Adding field 'Image.is_public'
db.add_column('imagestore_image', 'is_public', self.gf('django.db.models.fields.BooleanField')(default=True), keep_default=False)
# Adding field 'Image.category'
db.add_column('imagestore_image', 'category', self.gf('django.db.models.fields.related.ForeignKey')(related_name='images', null=True, to=orm['imagestore.Category'], blank=True), keep_default=False)
# Deleting field 'Image.album'
db.delete_column('imagestore_image', 'album_id')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,93 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
from django.contrib.auth.management import create_permissions
from django.contrib.auth.models import User, Permission
from django.db.models import get_app
class Migration(DataMigration):
def forwards(self, orm):
app = get_app('imagestore')
create_permissions(app, (), 2)
add_image_permission = Permission.objects.get_by_natural_key('add_image', 'imagestore', 'image')
add_album_permission = Permission.objects.get_by_natural_key('add_album', 'imagestore', 'album')
change_image_permission = Permission.objects.get_by_natural_key('change_image', 'imagestore', 'image')
change_album_permission = Permission.objects.get_by_natural_key('change_album', 'imagestore', 'album')
delete_image_permission = Permission.objects.get_by_natural_key('delete_image', 'imagestore','image')
delete_album_permission = Permission.objects.get_by_natural_key('delete_album', 'imagestore', 'album')
for user in User.objects.all():
user.user_permissions.add(add_image_permission, add_album_permission,)
user.user_permissions.add(change_image_permission, change_album_permission,)
user.user_permissions.add(delete_image_permission, delete_album_permission,)
user.save()
def backwards(self, orm):
"Write your backwards methods here."
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,89 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Album.name'
db.alter_column('imagestore_album', 'name', self.gf('django.db.models.fields.CharField')(max_length=20))
# Changing field 'Image.title'
db.alter_column('imagestore_image', 'title', self.gf('django.db.models.fields.CharField')(max_length=20, null=True))
def backwards(self, orm):
# Changing field 'Album.name'
db.alter_column('imagestore_album', 'name', self.gf('django.db.models.fields.CharField')(max_length=200))
# Changing field 'Image.title'
db.alter_column('imagestore_image', 'title', self.gf('django.db.models.fields.CharField')(max_length=200, null=True))
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,172 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
try:
from places.models import GeoPlace
except:
GeoPlace = None
class Migration(SchemaMigration):
def forwards(self, orm):
if GeoPlace:
# Adding field 'Image.place'
db.add_column('imagestore_image', 'place', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='images', null=True, to=orm['places.GeoPlace']), keep_default=False)
def backwards(self, orm):
# Deleting field 'Image.place'
db.delete_column('imagestore_image', 'place_id')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'place': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['places.GeoPlace']"}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
},
'places.geoplace': {
'Meta': {'object_name': 'GeoPlace'},
'addional_info': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'imagestore_tag': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'latitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'longtitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'metro': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'minuses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'near_objects': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'near_objects_rel_+'", 'null': 'True', 'to': "orm['places.GeoPlace']"}),
'near_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'path_to': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'phone': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'pluses': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'tags': ('tagging.fields.TagField', [], {}),
'topic': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Topic']", 'null': 'True', 'blank': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['places.PlaceType']", 'null': 'None', 'blank': 'None'}),
'work_time': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
},
'places.placetype': {
'Meta': {'object_name': 'PlaceType'},
'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Forum']", 'null': 'True', 'blank': 'True'}),
'forum_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'icon_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'name_plural': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'path_to_image': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
},
'pybb.category': {
'Meta': {'ordering': "['position']", 'object_name': 'Category'},
'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'})
},
'pybb.forum': {
'Meta': {'ordering': "['position']", 'object_name': 'Forum'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'forums'", 'to': "orm['pybb.Category']"}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'headline': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'moderators': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'readed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'readed_forums'", 'symmetrical': 'False', 'through': "orm['pybb.ForumReadTracker']", 'to': "orm['auth.User']"}),
'topic_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'pybb.forumreadtracker': {
'Meta': {'object_name': 'ForumReadTracker'},
'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Forum']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time_stamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'pybb.topic': {
'Meta': {'ordering': "['-created']", 'object_name': 'Topic'},
'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'forum': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'topics'", 'to': "orm['pybb.Forum']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'readed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'readed_topics'", 'symmetrical': 'False', 'through': "orm['pybb.TopicReadTracker']", 'to': "orm['auth.User']"}),
'sticky': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'subscriptions'", 'blank': 'True', 'to': "orm['auth.User']"}),
'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'views': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'})
},
'pybb.topicreadtracker': {
'Meta': {'object_name': 'TopicReadTracker'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time_stamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'topic': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Topic']", 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,98 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'AlbumUpload'
db.create_table('imagestore_albumupload', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('zip_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
('album', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['imagestore.Album'], null=True, blank=True)),
('new_album_name', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
('tags', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
))
db.send_create_signal('imagestore', ['AlbumUpload'])
def backwards(self, orm):
# Deleting model 'AlbumUpload'
db.delete_table('imagestore_albumupload')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.albumupload': {
'Meta': {'object_name': 'AlbumUpload'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_album_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'tags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'zip_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,92 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Album.order'
db.add_column('imagestore_album', 'order', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False)
def backwards(self, orm):
# Deleting field 'Album.order'
db.delete_column('imagestore_album', 'order')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.albumupload': {
'Meta': {'object_name': 'AlbumUpload'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_album_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'tags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'zip_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,92 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Image.title'
db.alter_column('imagestore_image', 'title', self.gf('django.db.models.fields.CharField')(max_length=100, null=True))
def backwards(self, orm):
# Changing field 'Image.title'
db.alter_column('imagestore_image', 'title', self.gf('django.db.models.fields.CharField')(max_length=20, null=True))
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.albumupload': {
'Meta': {'object_name': 'AlbumUpload'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_album_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'tags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'zip_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,92 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Album.name'
db.alter_column('imagestore_album', 'name', self.gf('django.db.models.fields.CharField')(max_length=100))
def backwards(self, orm):
# Changing field 'Album.name'
db.alter_column('imagestore_album', 'name', self.gf('django.db.models.fields.CharField')(max_length=20))
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'imagestore.album': {
'Meta': {'ordering': "('created', 'name')", 'object_name': 'Album'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'head': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'head_of'", 'null': 'True', 'to': "orm['imagestore.Image']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'albums'", 'null': 'True', 'to': "orm['auth.User']"})
},
'imagestore.albumupload': {
'Meta': {'object_name': 'AlbumUpload'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['imagestore.Album']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_album_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'tags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'zip_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'})
},
'imagestore.image': {
'Meta': {'ordering': "('order', 'id')", 'object_name': 'Image'},
'album': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['imagestore.Album']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'tags': ('tagging.fields.TagField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'images'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['imagestore']

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from imagestore.utils import load_class, get_model_string
from django.conf import settings
Album = load_class(getattr(settings, 'IMAGESTORE_ALBUM_MODEL', 'imagestore.models.album.Album'))
Image = load_class(getattr(settings, 'IMAGESTORE_IMAGE_MODEL', 'imagestore.models.image.Image'))
# This labels and classnames used to generate permissons labels
image_applabel = Image._meta.app_label
image_classname = Image.__name__.lower()
album_applabel = Album._meta.app_label
album_classname = Album.__name__.lower()
from upload import AlbumUpload

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from bases.album import BaseAlbum
from django.utils.translation import ugettext_lazy as _
from imagestore.utils import load_class, get_model_string
class Album(BaseAlbum):
class Meta(BaseAlbum.Meta):
abstract = False
verbose_name = _('Album')
verbose_name_plural = _('Albums')
app_label = 'imagestore'

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from django.db import models
from django.db.models import permalink
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from sorl.thumbnail import get_thumbnail
try:
from django.contrib.auth import get_user_model
User = get_user_model()
except ImportError:
from django.contrib.auth.models import User
try:
import Image as PILImage
except ImportError:
from PIL import Image as PILImage
from imagestore.utils import get_model_string
SELF_MANAGE = getattr(settings, 'IMAGESTORE_SELF_MANAGE', True)
class BaseAlbum(models.Model):
class Meta(object):
abstract = True
ordering = ('order', 'created', 'name')
permissions = (
('moderate_albums', 'View, update and delete any album'),
)
user = models.ForeignKey(User, verbose_name=_('User'), null=True, blank=True, related_name='albums')
name = models.CharField(_('Name'), max_length=100, blank=False, null=False)
created = models.DateTimeField(_('Created'), auto_now_add=True)
updated = models.DateTimeField(_('Updated'), auto_now=True)
is_public = models.BooleanField(_('Is public'), default=True)
head = models.ForeignKey(get_model_string('Image'), related_name='head_of', null=True, blank=True, on_delete=models.SET_NULL)
order = models.IntegerField(_('Order'), default=0)
def get_head(self):
if self.head:
return self.head
else:
if self.images.all().count()>0:
self.head = self.images.all()[0]
self.save()
return self.head
else:
return None
@permalink
def get_absolute_url(self):
return 'imagestore:album', (), {'album_id': self.id}
def __unicode__(self):
return self.name
def admin_thumbnail(self):
img = self.get_head()
if img:
try:
return '<img src="%s">' % get_thumbnail(img.image, '100x100', crop='center').url
except IOError:
return 'IOError'
return _('Empty album')
admin_thumbnail.short_description = _('Head')
admin_thumbnail.allow_tags = True

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from django.db import models
from django.db.models import permalink
from sorl.thumbnail.helpers import ThumbnailError
from tagging.fields import TagField
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from sorl.thumbnail import ImageField, get_thumbnail
from django.contrib.auth.models import Permission
from django.db.models.signals import post_save
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
try:
from django.contrib.auth import get_user_model
User = get_user_model()
except ImportError:
from django.contrib.auth.models import User
try:
import Image as PILImage
except ImportError:
from PIL import Image as PILImage
from imagestore.utils import get_file_path, get_model_string
SELF_MANAGE = getattr(settings, 'IMAGESTORE_SELF_MANAGE', True)
class BaseImage(models.Model):
class Meta(object):
abstract = True
ordering = ('order', 'id')
permissions = (
('moderate_images', 'View, update and delete any image'),
)
title = models.CharField(_('Title'), max_length=100, blank=True, null=True)
description = models.TextField(_('Description'), blank=True, null=True)
tags = TagField(_('Tags'), blank=True)
order = models.IntegerField(_('Order'), default=0)
image = ImageField(verbose_name = _('File'), upload_to=get_file_path)
user = models.ForeignKey(User, verbose_name=_('User'), null=True, blank=True, related_name='images')
created = models.DateTimeField(_('Created'), auto_now_add=True, null=True)
updated = models.DateTimeField(_('Updated'), auto_now=True, null=True)
album = models.ForeignKey(get_model_string('Album'), verbose_name=_('Album'), null=True, blank=True, related_name='images')
@permalink
def get_absolute_url(self):
return 'imagestore:image', (), {'pk': self.id}
def __unicode__(self):
return '%s'% self.id
def admin_thumbnail(self):
try:
return '<img src="%s">' % get_thumbnail(self.image, '100x100', crop='center').url
except IOError:
return 'IOError'
except ThumbnailError, ex:
return 'ThumbnailError, %s' % ex.message
admin_thumbnail.short_description = _('Thumbnail')
admin_thumbnail.allow_tags = True
#noinspection PyUnusedLocal
def setup_imagestore_permissions(instance, created, **kwargs):
if not created:
return
try:
from imagestore.models import Album, Image
album_type = ContentType.objects.get(
#app_label=load_class('imagestore.models.Album')._meta.app_label,
app_label = Album._meta.app_label,
name='Album'
)
image_type = ContentType.objects.get(
#app_label=load_class('imagestore.models.Image')._meta.app_label,
app_label = Image._meta.app_label,
name='Image'
)
add_image_permission = Permission.objects.get(codename='add_image', content_type=image_type)
add_album_permission = Permission.objects.get(codename='add_album', content_type=album_type)
change_image_permission = Permission.objects.get(codename='change_image', content_type=image_type)
change_album_permission = Permission.objects.get(codename='change_album', content_type=album_type)
delete_image_permission = Permission.objects.get(codename='delete_image', content_type=image_type)
delete_album_permission = Permission.objects.get(codename='delete_album', content_type=album_type)
instance.user_permissions.add(add_image_permission, add_album_permission,)
instance.user_permissions.add(change_image_permission, change_album_permission,)
instance.user_permissions.add(delete_image_permission, delete_album_permission,)
except ObjectDoesNotExist:
# Permissions are not yet installed or conten does not created yet
# probaly this is first
pass
if SELF_MANAGE:
post_save.connect(setup_imagestore_permissions, User)

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__author__ = 'zeus'
from bases.image import BaseImage
from django.utils.translation import ugettext_lazy as _
from imagestore.utils import load_class, get_model_string
class Image(BaseImage):
class Meta(BaseImage.Meta):
abstract = False
verbose_name = _('Image')
verbose_name_plural = _('Images')
app_label = 'imagestore'

112
imagestore/models/upload.py Normal file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module
__author__ = 'zeus'
import os
import zipfile
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.core.files.base import ContentFile
try:
import Image as PILImage
except ImportError:
from PIL import Image as PILImage
from imagestore.models import Album, Image
TEMP_DIR = getattr(settings, 'TEMP_DIR', 'temp/')
def process_zipfile(uploaded_album):
if os.path.isfile(uploaded_album.zip_file.path):
# TODO: implement try-except here
zip = zipfile.ZipFile(uploaded_album.zip_file.path)
bad_file = zip.testzip()
if bad_file:
raise Exception('"%s" in the .zip archive is corrupt.' % bad_file)
if not uploaded_album.album:
uploaded_album.album = Album.objects.create(name=uploaded_album.new_album_name)
from cStringIO import StringIO
for filename in sorted(zip.namelist()):
if filename.startswith('__'): # do not process meta files
continue
print filename
data = zip.read(filename)
if len(data):
try:
# the following is taken from django.forms.fields.ImageField:
# load() could spot a truncated JPEG, but it loads the entire
# image in memory, which is a DoS vector. See #3848 and #18520.
# verify() must be called immediately after the constructor.
PILImage.open(StringIO(data)).verify()
except Exception, ex:
# if a "bad" file is found we just skip it.
print('Error verify image: %s' % ex.message)
continue
if hasattr(data, 'seek') and callable(data.seek):
print 'seeked'
data.seek(0)
try:
img = Image(album=uploaded_album.album)
img.image.save(filename, ContentFile(data))
img.save()
except Exception, ex:
print('error create Image: %s' % ex.message)
zip.close()
uploaded_album.delete()
upload_processor_function = getattr(settings, 'IMAGESTORE_UPLOAD_ALBUM_PROCESSOR', None)
upload_processor = process_zipfile
if upload_processor_function:
i = upload_processor_function.rfind('.')
module, attr = upload_processor_function[:i], upload_processor_function[i+1:]
try:
mod = import_module(module)
except ImportError as e:
raise ImproperlyConfigured('Error importing request processor module %s: "%s"' % (module, e))
try:
upload_processor = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured('Module "%s" does not define a "%s" callable request processor' % (module, attr))
class AlbumUpload(models.Model):
"""
Just re-written django-photologue GalleryUpload method
"""
zip_file = models.FileField(_('images file (.zip)'), upload_to=TEMP_DIR,
help_text=_('Select a .zip file of images to upload into a new Gallery.'))
album = models.ForeignKey(
Album,
null=True,
blank=True,
help_text=_('Select an album to add these images to. leave this empty to create a new album from the supplied title.')
)
new_album_name = models.CharField(
max_length=255,
blank=True,
verbose_name=_('New album name'),
help_text=_('If not empty new album with this name will be created and images will be upload to this album')
)
tags = models.CharField(max_length=255, blank=True, verbose_name=_('tags'))
class Meta(object):
verbose_name = _('Album upload')
verbose_name_plural = _('Album uploads')
app_label = 'imagestore'
def save(self, *args, **kwargs):
super(AlbumUpload, self).save(*args, **kwargs)
upload_processor(self)
def delete(self, *args, **kwargs):
storage, path = self.zip_file.storage, self.zip_file.path
super(AlbumUpload, self).delete(*args, **kwargs)
storage.delete(path)

View File

@@ -0,0 +1,107 @@
#category-list li{
font-size: 120%;
padding-bottom: 15px;
}
#category-list {
padding-bottom: 10px;
}
#controls {
font-size: 90%;
}
#controls .controls-group {
padding-top: 10px;
}
.image-description {
padding: 10px 0 10px 0;
}
.navigation {
text-align: center;
padding: 0 0 10px 0;
font-size: 110%;
}
.navigation .next-link {
margin-left: 100px;
}
img.current {
border: 1px red solid;
}
#image-view img.preview {
display: block;
margin: 0 auto 0 auto;
}
#image-view {
margin-right: 300px;
}
.album-list .album {
width: 160px;
height: 200px;
float: left;
padding: 5px;
margin: 10px;
}
.album-name {
width: 100%;
text-align: center;
}
.album-user {
width: 100%;
text-align: center;
font-size: 80%;
color: #CCC;
}
.album-head {
height: 150px;
width: 150px;
display: table-cell;
vertical-align: middle;
text-align: center;
}
.album-head a {
text-align: center;
}
.user-info {
padding: 20px 0;
}
.image-preview {
float: left;
width: 130px;
padding: 5px;
height: 160px;
}
.image-preview .image-title {
font-size: 80%;
}
.extra-fields {
font-size: 80%;
margin: 10px 0;
}
.extra-fields label {
color: #666666;
}
.basic-fields, .extra-fields {
display: block;
float: left;
}
.pagination .disabled {
display: none;
}

View File

@@ -0,0 +1,30 @@
prettyPhoto v3.1.4
© Copyright, Stephane Caron
http://www.no-margin-for-errors.com
============================= Released under =============================
Creative Commons 2.5
http://creativecommons.org/licenses/by/2.5/
OR
GPLV2 license
http://www.gnu.org/licenses/gpl-2.0.html
You are free to use prettyPhoto in commercial projects as long as the
copyright header is left intact.
============================ More information ============================
http://www.no-margin-for-errors.com/projects/prettyPhoto/
============================== Description ===============================
prettyPhoto is a jQuery based lightbox clone. Not only does it support images,
it also add support for videos, flash, YouTube, iFrame. It's a full blown
media modal box.
Please refer to http://www.no-margin-for-errors.com/projects/prettyPhoto/
for all the details on how to use.

View File

@@ -0,0 +1,170 @@
div.pp_default .pp_top,div.pp_default .pp_top .pp_middle,div.pp_default .pp_top .pp_left,div.pp_default .pp_top .pp_right,div.pp_default .pp_bottom,div.pp_default .pp_bottom .pp_left,div.pp_default .pp_bottom .pp_middle,div.pp_default .pp_bottom .pp_right{height:13px}
div.pp_default .pp_top .pp_left{background:url(../images/prettyPhoto/default/sprite.png) -78px -93px no-repeat}
div.pp_default .pp_top .pp_middle{background:url(../images/prettyPhoto/default/sprite_x.png) top left repeat-x}
div.pp_default .pp_top .pp_right{background:url(../images/prettyPhoto/default/sprite.png) -112px -93px no-repeat}
div.pp_default .pp_content .ppt{color:#f8f8f8}
div.pp_default .pp_content_container .pp_left{background:url(../images/prettyPhoto/default/sprite_y.png) -7px 0 repeat-y;padding-left:13px}
div.pp_default .pp_content_container .pp_right{background:url(../images/prettyPhoto/default/sprite_y.png) top right repeat-y;padding-right:13px}
div.pp_default .pp_next:hover{background:url(../images/prettyPhoto/default/sprite_next.png) center right no-repeat;cursor:pointer}
div.pp_default .pp_previous:hover{background:url(../images/prettyPhoto/default/sprite_prev.png) center left no-repeat;cursor:pointer}
div.pp_default .pp_expand{background:url(../images/prettyPhoto/default/sprite.png) 0 -29px no-repeat;cursor:pointer;width:28px;height:28px}
div.pp_default .pp_expand:hover{background:url(../images/prettyPhoto/default/sprite.png) 0 -56px no-repeat;cursor:pointer}
div.pp_default .pp_contract{background:url(../images/prettyPhoto/default/sprite.png) 0 -84px no-repeat;cursor:pointer;width:28px;height:28px}
div.pp_default .pp_contract:hover{background:url(../images/prettyPhoto/default/sprite.png) 0 -113px no-repeat;cursor:pointer}
div.pp_default .pp_close{width:30px;height:30px;background:url(../images/prettyPhoto/default/sprite.png) 2px 1px no-repeat;cursor:pointer}
div.pp_default .pp_gallery ul li a{background:url(../images/prettyPhoto/default/default_thumb.png) center center #f8f8f8;border:1px solid #aaa}
div.pp_default .pp_social{margin-top:7px}
div.pp_default .pp_gallery a.pp_arrow_previous,div.pp_default .pp_gallery a.pp_arrow_next{position:static;left:auto}
div.pp_default .pp_nav .pp_play,div.pp_default .pp_nav .pp_pause{background:url(../images/prettyPhoto/default/sprite.png) -51px 1px no-repeat;height:30px;width:30px}
div.pp_default .pp_nav .pp_pause{background-position:-51px -29px}
div.pp_default a.pp_arrow_previous,div.pp_default a.pp_arrow_next{background:url(../images/prettyPhoto/default/sprite.png) -31px -3px no-repeat;height:20px;width:20px;margin:4px 0 0}
div.pp_default a.pp_arrow_next{left:52px;background-position:-82px -3px}
div.pp_default .pp_content_container .pp_details{margin-top:5px}
div.pp_default .pp_nav{clear:none;height:30px;width:110px;position:relative}
div.pp_default .pp_nav .currentTextHolder{font-family:Georgia;font-style:italic;color:#999;font-size:11px;left:75px;line-height:25px;position:absolute;top:2px;margin:0;padding:0 0 0 10px}
div.pp_default .pp_close:hover,div.pp_default .pp_nav .pp_play:hover,div.pp_default .pp_nav .pp_pause:hover,div.pp_default .pp_arrow_next:hover,div.pp_default .pp_arrow_previous:hover{opacity:0.7}
div.pp_default .pp_description{font-size:11px;font-weight:700;line-height:14px;margin:5px 50px 5px 0}
div.pp_default .pp_bottom .pp_left{background:url(../images/prettyPhoto/default/sprite.png) -78px -127px no-repeat}
div.pp_default .pp_bottom .pp_middle{background:url(../images/prettyPhoto/default/sprite_x.png) bottom left repeat-x}
div.pp_default .pp_bottom .pp_right{background:url(../images/prettyPhoto/default/sprite.png) -112px -127px no-repeat}
div.pp_default .pp_loaderIcon{background:url(../images/prettyPhoto/default/loader.gif) center center no-repeat}
div.light_rounded .pp_top .pp_left{background:url(../images/prettyPhoto/light_rounded/sprite.png) -88px -53px no-repeat}
div.light_rounded .pp_top .pp_right{background:url(../images/prettyPhoto/light_rounded/sprite.png) -110px -53px no-repeat}
div.light_rounded .pp_next:hover{background:url(../images/prettyPhoto/light_rounded/btnNext.png) center right no-repeat;cursor:pointer}
div.light_rounded .pp_previous:hover{background:url(../images/prettyPhoto/light_rounded/btnPrevious.png) center left no-repeat;cursor:pointer}
div.light_rounded .pp_expand{background:url(../images/prettyPhoto/light_rounded/sprite.png) -31px -26px no-repeat;cursor:pointer}
div.light_rounded .pp_expand:hover{background:url(../images/prettyPhoto/light_rounded/sprite.png) -31px -47px no-repeat;cursor:pointer}
div.light_rounded .pp_contract{background:url(../images/prettyPhoto/light_rounded/sprite.png) 0 -26px no-repeat;cursor:pointer}
div.light_rounded .pp_contract:hover{background:url(../images/prettyPhoto/light_rounded/sprite.png) 0 -47px no-repeat;cursor:pointer}
div.light_rounded .pp_close{width:75px;height:22px;background:url(../images/prettyPhoto/light_rounded/sprite.png) -1px -1px no-repeat;cursor:pointer}
div.light_rounded .pp_nav .pp_play{background:url(../images/prettyPhoto/light_rounded/sprite.png) -1px -100px no-repeat;height:15px;width:14px}
div.light_rounded .pp_nav .pp_pause{background:url(../images/prettyPhoto/light_rounded/sprite.png) -24px -100px no-repeat;height:15px;width:14px}
div.light_rounded .pp_arrow_previous{background:url(../images/prettyPhoto/light_rounded/sprite.png) 0 -71px no-repeat}
div.light_rounded .pp_arrow_next{background:url(../images/prettyPhoto/light_rounded/sprite.png) -22px -71px no-repeat}
div.light_rounded .pp_bottom .pp_left{background:url(../images/prettyPhoto/light_rounded/sprite.png) -88px -80px no-repeat}
div.light_rounded .pp_bottom .pp_right{background:url(../images/prettyPhoto/light_rounded/sprite.png) -110px -80px no-repeat}
div.dark_rounded .pp_top .pp_left{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -88px -53px no-repeat}
div.dark_rounded .pp_top .pp_right{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -110px -53px no-repeat}
div.dark_rounded .pp_content_container .pp_left{background:url(../images/prettyPhoto/dark_rounded/contentPattern.png) top left repeat-y}
div.dark_rounded .pp_content_container .pp_right{background:url(../images/prettyPhoto/dark_rounded/contentPattern.png) top right repeat-y}
div.dark_rounded .pp_next:hover{background:url(../images/prettyPhoto/dark_rounded/btnNext.png) center right no-repeat;cursor:pointer}
div.dark_rounded .pp_previous:hover{background:url(../images/prettyPhoto/dark_rounded/btnPrevious.png) center left no-repeat;cursor:pointer}
div.dark_rounded .pp_expand{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -31px -26px no-repeat;cursor:pointer}
div.dark_rounded .pp_expand:hover{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -31px -47px no-repeat;cursor:pointer}
div.dark_rounded .pp_contract{background:url(../images/prettyPhoto/dark_rounded/sprite.png) 0 -26px no-repeat;cursor:pointer}
div.dark_rounded .pp_contract:hover{background:url(../images/prettyPhoto/dark_rounded/sprite.png) 0 -47px no-repeat;cursor:pointer}
div.dark_rounded .pp_close{width:75px;height:22px;background:url(../images/prettyPhoto/dark_rounded/sprite.png) -1px -1px no-repeat;cursor:pointer}
div.dark_rounded .pp_description{margin-right:85px;color:#fff}
div.dark_rounded .pp_nav .pp_play{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -1px -100px no-repeat;height:15px;width:14px}
div.dark_rounded .pp_nav .pp_pause{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -24px -100px no-repeat;height:15px;width:14px}
div.dark_rounded .pp_arrow_previous{background:url(../images/prettyPhoto/dark_rounded/sprite.png) 0 -71px no-repeat}
div.dark_rounded .pp_arrow_next{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -22px -71px no-repeat}
div.dark_rounded .pp_bottom .pp_left{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -88px -80px no-repeat}
div.dark_rounded .pp_bottom .pp_right{background:url(../images/prettyPhoto/dark_rounded/sprite.png) -110px -80px no-repeat}
div.dark_rounded .pp_loaderIcon{background:url(../images/prettyPhoto/dark_rounded/loader.gif) center center no-repeat}
div.dark_square .pp_left,div.dark_square .pp_middle,div.dark_square .pp_right,div.dark_square .pp_content{background:#000}
div.dark_square .pp_description{color:#fff;margin:0 85px 0 0}
div.dark_square .pp_loaderIcon{background:url(../images/prettyPhoto/dark_square/loader.gif) center center no-repeat}
div.dark_square .pp_expand{background:url(../images/prettyPhoto/dark_square/sprite.png) -31px -26px no-repeat;cursor:pointer}
div.dark_square .pp_expand:hover{background:url(../images/prettyPhoto/dark_square/sprite.png) -31px -47px no-repeat;cursor:pointer}
div.dark_square .pp_contract{background:url(../images/prettyPhoto/dark_square/sprite.png) 0 -26px no-repeat;cursor:pointer}
div.dark_square .pp_contract:hover{background:url(../images/prettyPhoto/dark_square/sprite.png) 0 -47px no-repeat;cursor:pointer}
div.dark_square .pp_close{width:75px;height:22px;background:url(../images/prettyPhoto/dark_square/sprite.png) -1px -1px no-repeat;cursor:pointer}
div.dark_square .pp_nav{clear:none}
div.dark_square .pp_nav .pp_play{background:url(../images/prettyPhoto/dark_square/sprite.png) -1px -100px no-repeat;height:15px;width:14px}
div.dark_square .pp_nav .pp_pause{background:url(../images/prettyPhoto/dark_square/sprite.png) -24px -100px no-repeat;height:15px;width:14px}
div.dark_square .pp_arrow_previous{background:url(../images/prettyPhoto/dark_square/sprite.png) 0 -71px no-repeat}
div.dark_square .pp_arrow_next{background:url(../images/prettyPhoto/dark_square/sprite.png) -22px -71px no-repeat}
div.dark_square .pp_next:hover{background:url(../images/prettyPhoto/dark_square/btnNext.png) center right no-repeat;cursor:pointer}
div.dark_square .pp_previous:hover{background:url(../images/prettyPhoto/dark_square/btnPrevious.png) center left no-repeat;cursor:pointer}
div.light_square .pp_expand{background:url(../images/prettyPhoto/light_square/sprite.png) -31px -26px no-repeat;cursor:pointer}
div.light_square .pp_expand:hover{background:url(../images/prettyPhoto/light_square/sprite.png) -31px -47px no-repeat;cursor:pointer}
div.light_square .pp_contract{background:url(../images/prettyPhoto/light_square/sprite.png) 0 -26px no-repeat;cursor:pointer}
div.light_square .pp_contract:hover{background:url(../images/prettyPhoto/light_square/sprite.png) 0 -47px no-repeat;cursor:pointer}
div.light_square .pp_close{width:75px;height:22px;background:url(../images/prettyPhoto/light_square/sprite.png) -1px -1px no-repeat;cursor:pointer}
div.light_square .pp_nav .pp_play{background:url(../images/prettyPhoto/light_square/sprite.png) -1px -100px no-repeat;height:15px;width:14px}
div.light_square .pp_nav .pp_pause{background:url(../images/prettyPhoto/light_square/sprite.png) -24px -100px no-repeat;height:15px;width:14px}
div.light_square .pp_arrow_previous{background:url(../images/prettyPhoto/light_square/sprite.png) 0 -71px no-repeat}
div.light_square .pp_arrow_next{background:url(../images/prettyPhoto/light_square/sprite.png) -22px -71px no-repeat}
div.light_square .pp_next:hover{background:url(../images/prettyPhoto/light_square/btnNext.png) center right no-repeat;cursor:pointer}
div.light_square .pp_previous:hover{background:url(../images/prettyPhoto/light_square/btnPrevious.png) center left no-repeat;cursor:pointer}
div.facebook .pp_top .pp_left{background:url(../images/prettyPhoto/facebook/sprite.png) -88px -53px no-repeat}
div.facebook .pp_top .pp_middle{background:url(../images/prettyPhoto/facebook/contentPatternTop.png) top left repeat-x}
div.facebook .pp_top .pp_right{background:url(../images/prettyPhoto/facebook/sprite.png) -110px -53px no-repeat}
div.facebook .pp_content_container .pp_left{background:url(../images/prettyPhoto/facebook/contentPatternLeft.png) top left repeat-y}
div.facebook .pp_content_container .pp_right{background:url(../images/prettyPhoto/facebook/contentPatternRight.png) top right repeat-y}
div.facebook .pp_expand{background:url(../images/prettyPhoto/facebook/sprite.png) -31px -26px no-repeat;cursor:pointer}
div.facebook .pp_expand:hover{background:url(../images/prettyPhoto/facebook/sprite.png) -31px -47px no-repeat;cursor:pointer}
div.facebook .pp_contract{background:url(../images/prettyPhoto/facebook/sprite.png) 0 -26px no-repeat;cursor:pointer}
div.facebook .pp_contract:hover{background:url(../images/prettyPhoto/facebook/sprite.png) 0 -47px no-repeat;cursor:pointer}
div.facebook .pp_close{width:22px;height:22px;background:url(../images/prettyPhoto/facebook/sprite.png) -1px -1px no-repeat;cursor:pointer}
div.facebook .pp_description{margin:0 37px 0 0}
div.facebook .pp_loaderIcon{background:url(../images/prettyPhoto/facebook/loader.gif) center center no-repeat}
div.facebook .pp_arrow_previous{background:url(../images/prettyPhoto/facebook/sprite.png) 0 -71px no-repeat;height:22px;margin-top:0;width:22px}
div.facebook .pp_arrow_previous.disabled{background-position:0 -96px;cursor:default}
div.facebook .pp_arrow_next{background:url(../images/prettyPhoto/facebook/sprite.png) -32px -71px no-repeat;height:22px;margin-top:0;width:22px}
div.facebook .pp_arrow_next.disabled{background-position:-32px -96px;cursor:default}
div.facebook .pp_nav{margin-top:0}
div.facebook .pp_nav p{font-size:15px;padding:0 3px 0 4px}
div.facebook .pp_nav .pp_play{background:url(../images/prettyPhoto/facebook/sprite.png) -1px -123px no-repeat;height:22px;width:22px}
div.facebook .pp_nav .pp_pause{background:url(../images/prettyPhoto/facebook/sprite.png) -32px -123px no-repeat;height:22px;width:22px}
div.facebook .pp_next:hover{background:url(../images/prettyPhoto/facebook/btnNext.png) center right no-repeat;cursor:pointer}
div.facebook .pp_previous:hover{background:url(../images/prettyPhoto/facebook/btnPrevious.png) center left no-repeat;cursor:pointer}
div.facebook .pp_bottom .pp_left{background:url(../images/prettyPhoto/facebook/sprite.png) -88px -80px no-repeat}
div.facebook .pp_bottom .pp_middle{background:url(../images/prettyPhoto/facebook/contentPatternBottom.png) top left repeat-x}
div.facebook .pp_bottom .pp_right{background:url(../images/prettyPhoto/facebook/sprite.png) -110px -80px no-repeat}
div.pp_pic_holder a:focus{outline:none}
div.pp_overlay{background:#000;display:none;left:0;position:absolute;top:0;width:100%;z-index:9500}
div.pp_pic_holder{display:none;position:absolute;width:100px;z-index:10000}
.pp_content{height:40px;min-width:40px}
* html .pp_content{width:40px}
.pp_content_container{position:relative;text-align:left;width:100%}
.pp_content_container .pp_left{padding-left:20px}
.pp_content_container .pp_right{padding-right:20px}
.pp_content_container .pp_details{float:left;margin:10px 0 2px}
.pp_description{display:none;margin:0}
.pp_social{float:left;margin:0}
.pp_social .facebook{float:left;margin-left:5px;width:55px;overflow:hidden}
.pp_social .twitter{float:left}
.pp_nav{clear:right;float:left;margin:3px 10px 0 0}
.pp_nav p{float:left;white-space:nowrap;margin:2px 4px}
.pp_nav .pp_play,.pp_nav .pp_pause{float:left;margin-right:4px;text-indent:-10000px}
a.pp_arrow_previous,a.pp_arrow_next{display:block;float:left;height:15px;margin-top:3px;overflow:hidden;text-indent:-10000px;width:14px}
.pp_hoverContainer{position:absolute;top:0;width:100%;z-index:2000}
.pp_gallery{display:none;left:50%;margin-top:-50px;position:absolute;z-index:10000}
.pp_gallery div{float:left;overflow:hidden;position:relative}
.pp_gallery ul{float:left;height:35px;position:relative;white-space:nowrap;margin:0 0 0 5px;padding:0}
.pp_gallery ul a{border:1px rgba(0,0,0,0.5) solid;display:block;float:left;height:33px;overflow:hidden}
.pp_gallery ul a img{border:0}
.pp_gallery li{display:block;float:left;margin:0 5px 0 0;padding:0}
.pp_gallery li.default a{background:url(../images/prettyPhoto/facebook/default_thumbnail.gif) 0 0 no-repeat;display:block;height:33px;width:50px}
.pp_gallery .pp_arrow_previous,.pp_gallery .pp_arrow_next{margin-top:7px!important}
a.pp_next{background:url(../images/prettyPhoto/light_rounded/btnNext.png) 10000px 10000px no-repeat;display:block;float:right;height:100%;text-indent:-10000px;width:49%}
a.pp_previous{background:url(../images/prettyPhoto/light_rounded/btnNext.png) 10000px 10000px no-repeat;display:block;float:left;height:100%;text-indent:-10000px;width:49%}
a.pp_expand,a.pp_contract{cursor:pointer;display:none;height:20px;position:absolute;right:30px;text-indent:-10000px;top:10px;width:20px;z-index:20000}
a.pp_close{position:absolute;right:0;top:0;display:block;line-height:22px;text-indent:-10000px}
.pp_loaderIcon{display:block;height:24px;left:50%;position:absolute;top:50%;width:24px;margin:-12px 0 0 -12px}
#pp_full_res{line-height:1!important}
#pp_full_res .pp_inline{text-align:left}
#pp_full_res .pp_inline p{margin:0 0 15px}
div.ppt{color:#fff;display:none;font-size:17px;z-index:9999;margin:0 0 5px 15px}
div.pp_default .pp_content,div.light_rounded .pp_content{background-color:#fff}
div.pp_default #pp_full_res .pp_inline,div.light_rounded .pp_content .ppt,div.light_rounded #pp_full_res .pp_inline,div.light_square .pp_content .ppt,div.light_square #pp_full_res .pp_inline,div.facebook .pp_content .ppt,div.facebook #pp_full_res .pp_inline{color:#000}
div.pp_default .pp_gallery ul li a:hover,div.pp_default .pp_gallery ul li.selected a,.pp_gallery ul a:hover,.pp_gallery li.selected a{border-color:#fff}
div.pp_default .pp_details,div.light_rounded .pp_details,div.dark_rounded .pp_details,div.dark_square .pp_details,div.light_square .pp_details,div.facebook .pp_details{position:relative}
div.light_rounded .pp_top .pp_middle,div.light_rounded .pp_content_container .pp_left,div.light_rounded .pp_content_container .pp_right,div.light_rounded .pp_bottom .pp_middle,div.light_square .pp_left,div.light_square .pp_middle,div.light_square .pp_right,div.light_square .pp_content,div.facebook .pp_content{background:#fff}
div.light_rounded .pp_description,div.light_square .pp_description{margin-right:85px}
div.light_rounded .pp_gallery a.pp_arrow_previous,div.light_rounded .pp_gallery a.pp_arrow_next,div.dark_rounded .pp_gallery a.pp_arrow_previous,div.dark_rounded .pp_gallery a.pp_arrow_next,div.dark_square .pp_gallery a.pp_arrow_previous,div.dark_square .pp_gallery a.pp_arrow_next,div.light_square .pp_gallery a.pp_arrow_previous,div.light_square .pp_gallery a.pp_arrow_next{margin-top:12px!important}
div.light_rounded .pp_arrow_previous.disabled,div.dark_rounded .pp_arrow_previous.disabled,div.dark_square .pp_arrow_previous.disabled,div.light_square .pp_arrow_previous.disabled{background-position:0 -87px;cursor:default}
div.light_rounded .pp_arrow_next.disabled,div.dark_rounded .pp_arrow_next.disabled,div.dark_square .pp_arrow_next.disabled,div.light_square .pp_arrow_next.disabled{background-position:-22px -87px;cursor:default}
div.light_rounded .pp_loaderIcon,div.light_square .pp_loaderIcon{background:url(../images/prettyPhoto/light_rounded/loader.gif) center center no-repeat}
div.dark_rounded .pp_top .pp_middle,div.dark_rounded .pp_content,div.dark_rounded .pp_bottom .pp_middle{background:url(../images/prettyPhoto/dark_rounded/contentPattern.png) top left repeat}
div.dark_rounded .currentTextHolder,div.dark_square .currentTextHolder{color:#c4c4c4}
div.dark_rounded #pp_full_res .pp_inline,div.dark_square #pp_full_res .pp_inline{color:#fff}
.pp_top,.pp_bottom{height:20px;position:relative}
* html .pp_top,* html .pp_bottom{padding:0 20px}
.pp_top .pp_left,.pp_bottom .pp_left{height:20px;left:0;position:absolute;width:20px}
.pp_top .pp_middle,.pp_bottom .pp_middle{height:20px;left:20px;position:absolute;right:20px}
* html .pp_top .pp_middle,* html .pp_bottom .pp_middle{left:0;position:static}
.pp_top .pp_right,.pp_bottom .pp_right{height:20px;left:auto;position:absolute;right:0;top:0;width:20px}
.pp_fade,.pp_gallery li.default a img{display:none}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show More