basic text management
This commit is contained in:
commit
e55e78ba2d
19 changed files with 499 additions and 0 deletions
2
.bzrignore
Normal file
2
.bzrignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
host_settings/*
|
||||
media/*
|
0
__init__.py
Normal file
0
__init__.py
Normal file
BIN
__init__.pyc
Normal file
BIN
__init__.pyc
Normal file
Binary file not shown.
BIN
dev.sqlite
Normal file
BIN
dev.sqlite
Normal file
Binary file not shown.
11
manage.py
Executable file
11
manage.py
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/python
|
||||
from django.core.management import execute_manager
|
||||
try:
|
||||
import settings # Assumed to be in the same directory.
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
execute_manager(settings)
|
113
monitor.py
Normal file
113
monitor.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
import threading
|
||||
import atexit
|
||||
import Queue
|
||||
|
||||
_interval = 1.0
|
||||
_times = {}
|
||||
_files = []
|
||||
|
||||
_running = False
|
||||
_queue = Queue.Queue()
|
||||
_lock = threading.Lock()
|
||||
|
||||
def _restart(path):
|
||||
_queue.put(True)
|
||||
prefix = 'monitor (pid=%d):' % os.getpid()
|
||||
print >> sys.stderr, '%s Change detected to \'%s\'.' % (prefix, path)
|
||||
print >> sys.stderr, '%s Triggering process restart.' % prefix
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
|
||||
def _modified(path):
|
||||
try:
|
||||
# If path doesn't denote a file and were previously
|
||||
# tracking it, then it has been removed or the file type
|
||||
# has changed so force a restart. If not previously
|
||||
# tracking the file then we can ignore it as probably
|
||||
# pseudo reference such as when file extracted from a
|
||||
# collection of modules contained in a zip file.
|
||||
|
||||
if not os.path.isfile(path):
|
||||
return path in _times
|
||||
|
||||
# Check for when file last modified.
|
||||
|
||||
mtime = os.stat(path).st_mtime
|
||||
if path not in _times:
|
||||
_times[path] = mtime
|
||||
|
||||
# Force restart when modification time has changed, even
|
||||
# if time now older, as that could indicate older file
|
||||
# has been restored.
|
||||
|
||||
if mtime != _times[path]:
|
||||
return True
|
||||
except:
|
||||
# If any exception occured, likely that file has been
|
||||
# been removed just before stat(), so force a restart.
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _monitor():
|
||||
while 1:
|
||||
# Check modification times on all files in sys.modules.
|
||||
|
||||
for module in sys.modules.values():
|
||||
if not hasattr(module, '__file__'):
|
||||
continue
|
||||
path = getattr(module, '__file__')
|
||||
if not path:
|
||||
continue
|
||||
if os.path.splitext(path)[1] in ['.pyc', '.pyo', '.pyd']:
|
||||
path = path[:-1]
|
||||
if _modified(path):
|
||||
return _restart(path)
|
||||
|
||||
# Check modification times on files which have
|
||||
# specifically been registered for monitoring.
|
||||
|
||||
for path in _files:
|
||||
if _modified(path):
|
||||
return _restart(path)
|
||||
|
||||
# Go to sleep for specified interval.
|
||||
|
||||
try:
|
||||
return _queue.get(timeout=_interval)
|
||||
except:
|
||||
pass
|
||||
|
||||
_thread = threading.Thread(target=_monitor)
|
||||
_thread.setDaemon(True)
|
||||
|
||||
def _exiting():
|
||||
try:
|
||||
_queue.put(True)
|
||||
except:
|
||||
pass
|
||||
_thread.join()
|
||||
|
||||
atexit.register(_exiting)
|
||||
|
||||
def track(path):
|
||||
if not path in _files:
|
||||
_files.append(path)
|
||||
|
||||
def start(interval=1.0):
|
||||
global _interval
|
||||
if interval < _interval:
|
||||
_interval = interval
|
||||
|
||||
global _running
|
||||
_lock.acquire()
|
||||
if not _running:
|
||||
_running = True
|
||||
_thread.start()
|
||||
_lock.release()
|
99
settings.py
Normal file
99
settings.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
# Written 2009 by j@mailb.org
|
||||
|
||||
import os
|
||||
from os.path import join
|
||||
from django.conf import global_settings
|
||||
|
||||
PROJECT_PATH = os.path.normpath(os.path.dirname(__file__))
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@domain.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
||||
DATABASE_NAME = 'dev.sqlite' # Or path to database file if using sqlite3.
|
||||
DATABASE_USER = '' # Not used with sqlite3.
|
||||
DATABASE_PASSWORD = '' # Not used with sqlite3.
|
||||
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
|
||||
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# If running in a Windows environment this must be set to the same as your
|
||||
# system time zone.
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
|
||||
# Absolute path to the directory that holds media.
|
||||
# Example: "/home/media/media.lawrence.com/"
|
||||
MEDIA_ROOT = join(PROJECT_PATH, 'media')
|
||||
STATIC_ROOT = join(PROJECT_PATH, 'static')
|
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash if there is a path component (optional in other cases).
|
||||
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||
MEDIA_URL = '/texts/'
|
||||
|
||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://foo.com/media/", "/media/".
|
||||
ADMIN_MEDIA_PREFIX = '/admin/media/'
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY = '@8+=n)(@(gv0ogqm6pnvs6ag@&qa3syb^qy8@#x7f68)cyrs(*'
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.load_template_source',
|
||||
'django.template.loaders.app_directories.load_template_source',
|
||||
# 'django.template.loaders.eggs.load_template_source',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'texts.urls'
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
join(PROJECT_PATH, 'templates'),
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.admin',
|
||||
'texts.text',
|
||||
)
|
||||
|
||||
try:
|
||||
import socket
|
||||
# hostname = socket.gethostname().replace('.','_')
|
||||
# exec "from host_settings.%s import *" % hostname
|
||||
local_settings_module = socket.gethostname().split(".")[0]
|
||||
if local_settings_module:
|
||||
execfile(os.path.join(PROJECT_PATH, "host_settings", "%s.py" % local_settings_module))
|
||||
except ImportError, e:
|
||||
raise e
|
||||
|
BIN
settings.pyc
Normal file
BIN
settings.pyc
Normal file
Binary file not shown.
0
text/__init__.py
Normal file
0
text/__init__.py
Normal file
27
text/admin.py
Normal file
27
text/admin.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
# Written 2009 by j@mailb.org
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
import models
|
||||
|
||||
|
||||
class TextAdmin(admin.ModelAdmin):
|
||||
search_fields = ['title', 'authors__name']
|
||||
list_display = ('title', 'version', 'file')
|
||||
list_filter = ('authors', )
|
||||
admin.site.register(models.Text, TextAdmin)
|
||||
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
search_fields = ['name']
|
||||
list_display = ('name', )
|
||||
admin.site.register(models.Author, AuthorAdmin)
|
||||
|
||||
|
||||
class LanguageAdmin(admin.ModelAdmin):
|
||||
search_fields = ['name']
|
||||
list_display = ('name', )
|
||||
admin.site.register(models.Language, LanguageAdmin)
|
||||
|
0
text/management/__init__.py
Normal file
0
text/management/__init__.py
Normal file
0
text/management/commands/__init__.py
Normal file
0
text/management/commands/__init__.py
Normal file
35
text/management/commands/import_text.py
Normal file
35
text/management/commands/import_text.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
# Written 2009 by j@mailb.org
|
||||
|
||||
import os
|
||||
from glob import glob
|
||||
|
||||
import oxlib
|
||||
import oxlib.normalize
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.conf import settings
|
||||
|
||||
from texts.text import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
import texts from media/text.
|
||||
"""
|
||||
help = 'import texts that are not in db.'
|
||||
args = ''
|
||||
|
||||
def handle(self, **options):
|
||||
for f in glob('%s/*/*/*' % settings.MEDIA_ROOT):
|
||||
if os.path.isfile(f):
|
||||
name = f[len(settings.MEDIA_ROOT)+1:]
|
||||
name = name.decode('utf-8')
|
||||
q = models.Text.objects.filter(file=name)
|
||||
if q.count() == 0:
|
||||
print 'adding', name
|
||||
text = models.Text()
|
||||
text.file.name = name
|
||||
text.parsePath()
|
||||
|
130
text/models.py
Normal file
130
text/models.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
# Written 2009 by j@mailb.org
|
||||
import os
|
||||
import re
|
||||
from glob import glob
|
||||
|
||||
import oxlib
|
||||
import oxlib.normalize
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
def getAuthor(name, name_sort=''):
|
||||
try:
|
||||
a = Author.objects.get(name=name)
|
||||
except Author.DoesNotExist:
|
||||
a = Author(name=name, name_sort=name_sort)
|
||||
a.save()
|
||||
return a
|
||||
|
||||
class Author(models.Model):
|
||||
name = models.CharField(blank=True, max_length=1000, unique=True)
|
||||
name_sort = models.CharField('Sort Name', blank=True, max_length=1000)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name_sort', )
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.name_sort:
|
||||
self.name_sort = oxlib.normalize.canonicalName(self.name)
|
||||
super(Author, self).save(*args, **kwargs)
|
||||
|
||||
class Language(models.Model):
|
||||
name = models.CharField(blank=True, max_length=1000)
|
||||
class Meta:
|
||||
ordering = ('name', )
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class Text(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
title = models.CharField(blank=True, max_length=1000)
|
||||
version = models.CharField(blank=True, max_length=1000)
|
||||
|
||||
file = models.FileField(upload_to='Incoming', blank=True)
|
||||
authors = models.ManyToManyField(Author, related_name='texts', blank=True)
|
||||
languages = models.ManyToManyField(Language, related_name='texts', blank=True)
|
||||
compilation = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ('title', )
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
def getComputedPath(self):
|
||||
authors = [a.name_sort for a in self.authors.all()]
|
||||
author = "; ".join(authors)
|
||||
if not author:
|
||||
author = "Unknown Author"
|
||||
if self.compilation:
|
||||
author += " (Ed.)"
|
||||
elif author.endswith("."):
|
||||
author = author[:-1] + "_"
|
||||
title = self.title
|
||||
if self.version:
|
||||
title += ".%s" % self.version
|
||||
extension = os.path.splitext(self.file.path)[1].lower()
|
||||
path = u"%s/%s/%s%s" % (author[0].upper(), author, title, extension)
|
||||
return os.path.join(settings.MEDIA_ROOT, path).encode('utf-8')
|
||||
|
||||
def parsePath(self):
|
||||
if self.file:
|
||||
match = re.compile('./(.*)/(.*)').findall(self.file.name)
|
||||
title = os.path.splitext(match[0][1])[0]
|
||||
compilation = False
|
||||
#FIXME: parse version
|
||||
version = re.compile("\.(\w+)$").findall(title)
|
||||
if version:
|
||||
version = version[0]
|
||||
title = title[:-(len(version) + 1)]
|
||||
else:
|
||||
version = ''
|
||||
authors = match[0][0]
|
||||
if authors.endswith("_"):
|
||||
authors = authors[:-1] + "."
|
||||
if authors.endswith(' (Ed.)'):
|
||||
authors = authors[:-len(' (Ed.)')]
|
||||
compilation = True
|
||||
if authors != 'Unknown Author':
|
||||
authors = [(oxlib.normalize.normalizeName(a), a) for a in authors.split("; ")]
|
||||
else:
|
||||
authors = []
|
||||
|
||||
self.title = title
|
||||
self.version = version
|
||||
self.compilation = compilation
|
||||
self.save(rename=False)
|
||||
for a in authors:
|
||||
author = getAuthor(a[0], a[1])
|
||||
self.authors.add(author)
|
||||
self.save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
rename = True
|
||||
if "rename" in kwargs:
|
||||
rename = kwargs['rename']
|
||||
del kwargs['rename']
|
||||
super(Text, self).save(*args, **kwargs)
|
||||
if rename and self.file:
|
||||
path = self.getComputedPath()
|
||||
if path != self.file.path:
|
||||
#FIXME: make sure path is not used by other test in system
|
||||
if not os.path.exists(os.path.dirname(path)):
|
||||
os.makedirs(os.path.dirname(path))
|
||||
old_dir = os.path.dirname(self.file.path)
|
||||
print 'old', self.file.path
|
||||
print 'new', path
|
||||
os.rename(self.file.path, path)
|
||||
#FIXME: remove old dir if its empy
|
||||
if not glob('%s/*' % old_dir):
|
||||
os.rmdir(old_dir)
|
||||
self.file.name = path[len(settings.MEDIA_ROOT) + 1:]
|
||||
#this could be recursive!
|
||||
self.save()
|
||||
|
23
text/tests.py
Normal file
23
text/tests.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
This file demonstrates two different styles of tests (one doctest and one
|
||||
unittest). These will both pass when you run "manage.py test".
|
||||
|
||||
Replace these with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.failUnlessEqual(1 + 1, 2)
|
||||
|
||||
__test__ = {"doctest": """
|
||||
Another way to test that 1 + 1 is equal to 2.
|
||||
|
||||
>>> 1 + 1 == 2
|
||||
True
|
||||
"""}
|
||||
|
1
text/views.py
Normal file
1
text/views.py
Normal file
|
@ -0,0 +1 @@
|
|||
# Create your views here.
|
30
urls.py
Normal file
30
urls.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.conf import settings
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# Example:
|
||||
# (r'^', include('texts.foo.urls')),
|
||||
|
||||
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
|
||||
# to INSTALLED_APPS to enable admin documentation:
|
||||
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
|
||||
# Uncomment the next line to enable the admin:
|
||||
(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += patterns('',
|
||||
(r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'),
|
||||
'django.views.static.serve',
|
||||
{'document_root': settings.MEDIA_ROOT}),
|
||||
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
|
||||
{'document_root': settings.STATIC_ROOT}),
|
||||
)
|
||||
|
||||
|
BIN
urls.pyc
Normal file
BIN
urls.pyc
Normal file
Binary file not shown.
28
wsgi/django.wsgi
Normal file
28
wsgi/django.wsgi
Normal file
|
@ -0,0 +1,28 @@
|
|||
# django.wsgi
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
|
||||
project_module = 'texts'
|
||||
|
||||
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
||||
|
||||
#using virtualenv's activate_this.py to reorder sys.path
|
||||
activate_this = os.path.join(root_dir, 'env', 'bin', 'activate_this.py')
|
||||
execfile(activate_this, dict(__file__=activate_this))
|
||||
|
||||
sys.path.append(root_dir)
|
||||
sys.path.append(os.path.join(root_dir, project_module))
|
||||
|
||||
#reload if this django.wsgi gets touched
|
||||
import monitor
|
||||
monitor.start(interval=1.0)
|
||||
|
||||
monitor.track(os.path.abspath(os.path.dirname(__file__)))
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = project_module + '.settings'
|
||||
|
||||
import django.core.handlers.wsgi
|
||||
|
||||
application = django.core.handlers.wsgi.WSGIHandler()
|
||||
|
Loading…
Reference in a new issue