Implement fargo
This commit is contained in:
parent
f5c743994b
commit
a5849218a3
51
README
51
README
|
@ -3,6 +3,7 @@ Fargo
|
|||
|
||||
To start do:
|
||||
|
||||
pip install -e .
|
||||
./manage.py migrate
|
||||
./manage.py runserver
|
||||
|
||||
|
@ -13,4 +14,52 @@ module, at its end.
|
|||
Settings
|
||||
========
|
||||
|
||||
FIXME
|
||||
Nothing for now.
|
||||
|
||||
Requesting a file from another application
|
||||
==========================================
|
||||
|
||||
Downloading a file from fargo is easy:
|
||||
- Open http://fargo/?pick=http://yoursite/pick-a-file/ in a popup or iframe
|
||||
- When the user choose a file it is returned to
|
||||
|
||||
http://yoursite/pick-a-file/?url=http://fargo/remote-download/name-of-the-file?token=xxxxxx
|
||||
|
||||
Your view on this URL should download the file from the given URL, do
|
||||
something with it then close the popup/iframe.
|
||||
|
||||
Download URL are only valid during 60 seconds after the request will return
|
||||
status 403. If the file has been removed a 404 is returned.
|
||||
|
||||
Displaying the list of files of a user
|
||||
======================================
|
||||
|
||||
There are two methods JSONP and JSON, both are totally insecure, protect
|
||||
them with your web server and IP limitations for now.
|
||||
|
||||
JSONP
|
||||
-----
|
||||
|
||||
* Add this to your page
|
||||
|
||||
<script>function callback(data) {
|
||||
// display the file list by modifying the DOM
|
||||
}</script>
|
||||
<script src="http://fargo/jsonp/?callback=callback"></script>
|
||||
|
||||
* data is structured like this:
|
||||
|
||||
[ { 'url': 'http://fargo/download/etc..', 'filename': 'facture.pdf'}, ... ]
|
||||
|
||||
JSON
|
||||
----
|
||||
|
||||
* Do a get on http://fargo/json/?username=john.doe
|
||||
|
||||
Showing an upload form
|
||||
======================
|
||||
|
||||
You can open an upload form to fargo by creating a popup or an iframe with
|
||||
location http://fargo/upload/. You can pass a parameter ?next=http://yoursite/
|
||||
if you want the user to come back to your site after the upload, to close the
|
||||
popup or the destroy the iframe.
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
from django.forms import ModelForm
|
||||
|
||||
from . import models
|
||||
|
||||
class UploadForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user')
|
||||
super(UploadForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.instance.user = self.user
|
||||
self.instance.document_filename = self.files['document_file'].name
|
||||
return super(UploadForm, self).save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
model = models.Document
|
||||
fields = ['document_file']
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Document',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('document_filename', models.CharField(max_length=512, verbose_name='document filename')),
|
||||
('document_file', models.FileField(upload_to=b'', verbose_name='file')),
|
||||
('creation', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
|
||||
('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
|
@ -1,3 +1,26 @@
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# Create your models here.
|
||||
class Document(models.Model):
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
verbose_name=_('user'))
|
||||
document_filename = models.CharField(
|
||||
verbose_name=_('document filename'),
|
||||
max_length=512)
|
||||
document_file = models.FileField(
|
||||
verbose_name=_('file'))
|
||||
creation = models.DateTimeField(
|
||||
verbose_name=_('creation date'),
|
||||
auto_now_add=True)
|
||||
|
||||
def delete(self):
|
||||
'''Delete file on model delete'''
|
||||
self.document_file.delete()
|
||||
super(Document, self).delete()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('document')
|
||||
verbose_name_plural = _('documents')
|
||||
ordering = ('-creation',)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import django_tables2 as tables
|
||||
from . import models
|
||||
|
||||
class DocumentTable(tables.Table):
|
||||
class Meta:
|
||||
model = models.Document
|
||||
fields = ('document_filename', 'creation')
|
||||
attrs = {'class': 'paleblue'}
|
|
@ -1,6 +1,188 @@
|
|||
from django.shortcuts import render
|
||||
import urlparse
|
||||
import urllib
|
||||
import logging
|
||||
from json import dumps
|
||||
|
||||
# Create your views here.
|
||||
from django.views.generic import CreateView, DeleteView, View, TemplateView
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
|
||||
from django.core import signing
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model, REDIRECT_FIELD_NAME
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
def home(request):
|
||||
return render(request, 'fargo/home.html')
|
||||
from django_tables2 import SingleTableMixin
|
||||
|
||||
from . import models, forms, tables
|
||||
|
||||
class Logger(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
class CommonUpload(Logger, CreateView):
|
||||
form_class = forms.UploadForm
|
||||
model = models.Document
|
||||
template_name = 'fargo/upload.html'
|
||||
|
||||
def get_form_kwargs(self, **kwargs):
|
||||
kwargs = super(CommonUpload, self).get_form_kwargs(**kwargs)
|
||||
kwargs['user'] = self.request.user
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
result = super(CommonUpload, self).form_valid(form)
|
||||
self.logger.info('user uploaded file %r(%s)',
|
||||
self.object.document_filename, self.object.pk)
|
||||
return result
|
||||
|
||||
class Upload(CommonUpload):
|
||||
def get_success_url(self):
|
||||
homepage = reverse('home')
|
||||
return self.request.GET.get(REDIRECT_FIELD_NAME, homepage)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if 'cancel' in request.POST:
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return super(Upload, self).post(request, *args, **kwargs)
|
||||
|
||||
class Documents(object):
|
||||
def get_queryset(self):
|
||||
return models.Document.objects.filter(user=self.request.user)
|
||||
|
||||
class Homepage(Documents, SingleTableMixin, CommonUpload):
|
||||
'''Show documents of users, eventually paginate and sort them.
|
||||
|
||||
If a pick_url parameter is passed, allow picking a file and returning a
|
||||
download URL to the pick_url.
|
||||
|
||||
'''
|
||||
template_name = 'fargo/home.html'
|
||||
form_class = forms.UploadForm
|
||||
table_class = tables.DocumentTable
|
||||
table_pagination = {
|
||||
'per_page': 5,
|
||||
}
|
||||
|
||||
def get_success_url(self):
|
||||
return ''
|
||||
|
||||
def can_pick(self):
|
||||
'''Check if user is currently picking a file'''
|
||||
# FIXME: we should check the pick URL is authorized
|
||||
return 'pick' in self.request.GET
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super(Homepage, self).get_context_data(**kwargs)
|
||||
if self.can_pick():
|
||||
ctx['pick'] = True
|
||||
return ctx
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if self.can_pick() and 'cancel' in request.POST:
|
||||
return HttpResponseRedirect(request.GET['pick'])
|
||||
return super(Upload, self).post(request, *args, **kwargs)
|
||||
|
||||
class Document(TemplateView):
|
||||
template_name = 'fargo/document.html'
|
||||
|
||||
class Delete(Logger, DeleteView):
|
||||
model = models.Document
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
result = super(Delete, self).delete(request, *args, **kwargs)
|
||||
messages.info(request, _('File %s deleted') % self.object.document_filename)
|
||||
self.logger.info('user deleted file %r(%s)',
|
||||
self.object.document_filename, self.object.pk)
|
||||
return result
|
||||
|
||||
def get_success_url(self):
|
||||
return '../..?%s' % self.request.META['QUERY_STRING']
|
||||
|
||||
class Pick(Logger, View):
|
||||
http_method_allowed = ['post']
|
||||
|
||||
def post(self, request, pk):
|
||||
document = get_object_or_404(models.Document, pk=pk, user=self.request.user)
|
||||
pick = self.request.GET['pick']
|
||||
token = signing.dumps(document.pk)
|
||||
url = reverse('remote_download', kwargs={'filename': document.document_filename})
|
||||
url += '?%s' % urllib.urlencode({'token': token})
|
||||
url = request.build_absolute_uri(url)
|
||||
scheme, netloc, path, qs, fragment = urlparse.urlsplit(pick)
|
||||
qs = urlparse.parse_qs(qs)
|
||||
print url
|
||||
qs['url'] = url
|
||||
qs = urllib.urlencode(qs, True)
|
||||
redirect = urlparse.urlunsplit((scheme, netloc, path, qs, fragment))
|
||||
print 'redirect', redirect
|
||||
self.logger.info('user picked file %r(%s) returned to %s',
|
||||
document.document_filename, document.pk, pick)
|
||||
return HttpResponseRedirect(redirect)
|
||||
|
||||
class Download(View):
|
||||
def get(self, request, pk, filename):
|
||||
document = get_object_or_404(models.Document, pk=pk, user=self.request.user)
|
||||
return self.return_document(document)
|
||||
|
||||
def return_document(self, document):
|
||||
response = HttpResponse(document.document_file.chunks(),
|
||||
content_type='application/octet-stream')
|
||||
response['Content-disposition'] = 'attachment'
|
||||
return response
|
||||
|
||||
class RemoteDownload(Download):
|
||||
'''Allow downloading any file given the URL contains a signed token'''
|
||||
def get(self, request, filename):
|
||||
if 'token' not in request.GET:
|
||||
return HttpResponseForbidden('missing token')
|
||||
# FIXME: maybe we should mark token as invalid after use using the
|
||||
# cache ?
|
||||
token = request.GET['token']
|
||||
# token are valid only 1 minute
|
||||
try:
|
||||
pk = signing.loads(token, max_age=60)
|
||||
except signing.SignatureExpired:
|
||||
return HttpResponseForbidden('token has expired')
|
||||
except signing.BadSignature:
|
||||
return HttpResponseForbidden('token signature is invalid')
|
||||
document = get_object_or_404(models.Document, pk=pk)
|
||||
return self.return_document(document)
|
||||
|
||||
class JSONP(Documents, View):
|
||||
def get_data(self, request):
|
||||
d = []
|
||||
for document in self.get_queryset():
|
||||
url = reverse('download', kwargs={'pk': document.pk,
|
||||
'filename': document.document_filename})
|
||||
url = request.build_absolute_uri(url)
|
||||
d.append({
|
||||
'filename': document.document_filename,
|
||||
'url': url,
|
||||
})
|
||||
return d
|
||||
|
||||
def get(self, request):
|
||||
callback = request.GET.get('callback', 'callback')
|
||||
s = '%s(%s)' % (callback.encode('ascii'),
|
||||
dumps(self.get_data(request)))
|
||||
return HttpResponse(s, content_type='application/javascript')
|
||||
|
||||
class JSON(JSONP):
|
||||
def get(self, request):
|
||||
username = request.GET.get('username')
|
||||
User = get_user_model()
|
||||
request.user = get_object_or_404(User, username=username)
|
||||
return HttpResponse(dumps(self.get_data(request)),
|
||||
content_type='application/json')
|
||||
|
||||
home = login_required(Homepage.as_view())
|
||||
document = login_required(Document.as_view())
|
||||
download = login_required(Download.as_view())
|
||||
upload = login_required(Upload.as_view())
|
||||
remote_download = RemoteDownload.as_view()
|
||||
delete = login_required(Delete.as_view())
|
||||
pick = login_required(Pick.as_view())
|
||||
jsonp = login_required(JSONP.as_view())
|
||||
json = login_required(JSON.as_view())
|
||||
|
|
|
@ -7,7 +7,7 @@ https://docs.djangoproject.com/en/1.7/topics/settings/
|
|||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||
"""
|
||||
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
|
||||
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS, STATICFILES_FINDERS
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
|
@ -37,7 +37,9 @@ INSTALLED_APPS = (
|
|||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_tables2',
|
||||
'sekizai',
|
||||
'gadjo',
|
||||
'fargo.fargo',
|
||||
)
|
||||
|
||||
|
@ -51,10 +53,15 @@ MIDDLEWARE_CLASSES = (
|
|||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS += (
|
||||
'django.core.context_processors.request',
|
||||
'sekizai.context_processors.sekizai',)
|
||||
|
||||
ROOT_URLCONF = 'fargo.urls'
|
||||
|
||||
WSGI_APPLICATION = 'fargo.wsgi.application'
|
||||
|
||||
STATICFILES_FINDERS = STATICFILES_FINDERS + ('gadjo.finders.XStaticFinder',)
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
|
@ -63,6 +70,7 @@ DATABASES = {
|
|||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'fargo.sqlite3'),
|
||||
'ATOMIC_REQUESTS': True,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,10 +95,6 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
|||
|
||||
LOCALE_PATHS = [os.path.join(BASE_DIR, 'fargo', 'locale')]
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS += ('sekizai.context_processors.sekizai',)
|
||||
|
||||
ATOMIC = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
||||
|
||||
|
|
|
@ -8,10 +8,16 @@
|
|||
</head>
|
||||
|
||||
<body {% block bodyattr %}{% endblock %}>
|
||||
{% if messages %}
|
||||
<ul id="messages">
|
||||
{% for message in messages %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div id="content">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% render_block "js-endpage" %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,5 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load sekizai_tags i18n %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
Salut tout le monde!
|
||||
{% addtoblock "css" %}<link rel="stylesheet" href="{{ STATIC_URL }}django_tables2/themes/paleblue/css/screen.css" />{% endaddtoblock %}
|
||||
<div id="user-files">
|
||||
{% render_table table "fargo/table.html" %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="submit" value="{% trans "Upload" %}">
|
||||
{% if pick %}
|
||||
<input type="submit" name="cancel" value="{% trans "Cancel" %}">
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
{% spaceless %}
|
||||
{% load django_tables2 %}
|
||||
{% load i18n %}
|
||||
{% load sekizai_tags %}
|
||||
{% load gadjo %}
|
||||
{% addtoblock "js" %}<script type="text/javascript" src="{% xstatic 'jquery' 'jquery.min.js' %}"></script>{% endaddtoblock %}
|
||||
<script>
|
||||
$(function () {
|
||||
$('tbody').on("click", "tr", function (event) {
|
||||
var $target = $(event.target)
|
||||
if (! $target.is('tr')) {
|
||||
$target = $target.parents('tr');
|
||||
}
|
||||
var url = $target.data('url');
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% if table.page %}
|
||||
<div class="table-container">
|
||||
{% endif %}
|
||||
{% block table %}
|
||||
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
|
||||
{% nospaceless %}
|
||||
{% block table.thead %}
|
||||
<thead>
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
{% if column.orderable %}
|
||||
<th {{ column.attrs.th.as_html }}><a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header }}</a></th>
|
||||
{% else %}
|
||||
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<th class="delete-column"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% endblock table.thead %}
|
||||
{% block table.tbody %}
|
||||
<tbody>
|
||||
{% for row in table.page.object_list|default:table.rows %} {# support pagination #}
|
||||
{% block table.tbody.row %}
|
||||
<tr data-url="{% url 'download' pk=row.record.pk filename=row.record.document_filename %}" class="{{ forloop.counter|divisibleby:2|yesno:"even,odd" }}"> {# avoid cycle for Django 1.2-1.6 compatibility #}
|
||||
{% for column, cell in row.items %}
|
||||
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
|
||||
{% endfor %}
|
||||
<td class="delete-column">
|
||||
{% if pick %}
|
||||
<form method="post" action="{% url 'pick' pk=row.record.pk %}{% querystring %}">
|
||||
{% csrf_token %}
|
||||
<button>{% trans "Pick" %}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" action="{% url 'delete' pk=row.record.pk %}{% querystring %}">
|
||||
{% csrf_token %}
|
||||
<button>{% trans "Delete" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
</tr>
|
||||
{% endblock table.tbody.row %}
|
||||
{% empty %}
|
||||
{% if table.empty_text %}
|
||||
{% block table.tbody.empty_text %}
|
||||
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
|
||||
{% endblock table.tbody.empty_text %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endblock table.tbody %}
|
||||
{% block table.tfoot %}
|
||||
<tfoot></tfoot>
|
||||
{% endblock table.tfoot %}
|
||||
{% endnospaceless %}
|
||||
</table>
|
||||
{% endblock table %}
|
||||
|
||||
{% if table.page %}
|
||||
{% with table.page.paginator.count as total %}
|
||||
{% with table.page.object_list|length as count %}
|
||||
{% block pagination %}
|
||||
<ul class="pagination">
|
||||
{% if table.page.has_previous %}
|
||||
{% nospaceless %}{% block pagination.previous %}<li class="previous"><a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}">{% trans "Previous" %}</a></li>{% endblock pagination.previous %}{% endnospaceless %}
|
||||
{% endif %}
|
||||
|
||||
{% if table.page.has_previous or table.page.has_next %}
|
||||
{% nospaceless %}{% block pagination.current %}<li class="current">{% blocktrans with table.page.number as current and table.paginator.num_pages as total %}Page {{ current }} of {{ total }}{% endblocktrans %}</li>{% endblock pagination.current %}{% endnospaceless %}
|
||||
{% endif %}
|
||||
|
||||
{% if table.page.has_next %}
|
||||
{% nospaceless %}{% block pagination.next %}<li class="next"><a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}">{% trans "Next" %}</a></li>{% endblock pagination.next %}{% endnospaceless %}
|
||||
{% endif %}
|
||||
|
||||
{% nospaceless %}{% block pagination.cardinality %}<li class="cardinality">{% if total != count %}{% blocktrans %}{{ count }} of {{ total }}{% endblocktrans %}{% else %}{{ total }}{% endif %} {% if total == 1 %}{{ table.data.verbose_name }}{% else %}{{ table.data.verbose_name_plural }}{% endif %}</li>{% endblock pagination.cardinality %}{% endnospaceless %}
|
||||
</ul>
|
||||
{% endblock pagination %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
|
@ -0,0 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load sekizai_tags i18n %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="submit" value="{% trans "Upload" %}">
|
||||
<input type="submit" name="cancel" value="{% trans "Cancel" %}">
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="submit" name="{% trans "Login" %}">
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,9 +1,19 @@
|
|||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
|
||||
from .fargo.views import home
|
||||
from .fargo.views import (home, jsonp, json, document, download, pick, delete, upload,
|
||||
remote_download)
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', home, name='home'),
|
||||
url(r'^jsonp/$', jsonp, name='jsonp'),
|
||||
url(r'^json/$', json, name='json'),
|
||||
url(r'^(?P<pk>\d+)/$', document, name='document'),
|
||||
url(r'^(?P<pk>\d+)/delete/$', delete, name='delete'),
|
||||
url(r'^(?P<pk>\d+)/pick/$', pick, name='pick'),
|
||||
url(r'^(?P<pk>\d+)/download/(?P<filename>[^/]*)$', download, name='download'),
|
||||
url(r'^upload/$', upload, name='upload'),
|
||||
url(r'^remote-download/(?P<filename>[^/]*)$', remote_download, name='remote_download'),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url('^accounts/', include('django.contrib.auth.urls')),
|
||||
)
|
||||
|
|
60
setup.py
60
setup.py
|
@ -4,6 +4,7 @@
|
|||
'''
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools.command.install_lib import install_lib as _install_lib
|
||||
from distutils.command.build import build as _build
|
||||
|
@ -36,40 +37,42 @@ class build(_build):
|
|||
class sdist(_sdist):
|
||||
sub_commands = [('compile_translations', None)] + _sdist.sub_commands
|
||||
|
||||
def run(self):
|
||||
print "creating VERSION file"
|
||||
if os.path.exists('VERSION'):
|
||||
os.remove('VERSION')
|
||||
version = get_version()
|
||||
version_file = open('VERSION', 'w')
|
||||
version_file.write(version)
|
||||
version_file.close()
|
||||
_sdist.run(self)
|
||||
print "removing VERSION file"
|
||||
if os.path.exists('VERSION'):
|
||||
os.remove('VERSION')
|
||||
|
||||
class install_lib(_install_lib):
|
||||
def run(self):
|
||||
self.run_command('compile_translations')
|
||||
_install_lib.run(self)
|
||||
|
||||
def get_version():
|
||||
import glob
|
||||
import re
|
||||
import os
|
||||
|
||||
version = None
|
||||
for d in glob.glob('*'):
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
module_file = os.path.join(d, '__init__.py')
|
||||
if not os.path.exists(module_file):
|
||||
continue
|
||||
for v in re.findall("""__version__ *= *['"](.*)['"]""",
|
||||
open(module_file).read()):
|
||||
assert version is None
|
||||
version = v
|
||||
if version:
|
||||
break
|
||||
assert version is not None
|
||||
'''Use the VERSION, if absent generates a version with git describe, if not
|
||||
tag exists, take 0.0.0- and add the length of the commit log.
|
||||
'''
|
||||
if os.path.exists('VERSION'):
|
||||
with open('VERSION', 'r') as v:
|
||||
return v.read()
|
||||
if os.path.exists('.git'):
|
||||
import subprocess
|
||||
p = subprocess.Popen(['git','describe','--dirty','--match=v*'],
|
||||
stdout=subprocess.PIPE)
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
result = p.communicate()[0]
|
||||
assert p.returncode == 0, 'git returned non-zero'
|
||||
new_version = result.split()[0][1:]
|
||||
assert new_version.split('-')[0] == version, '__version__ must match the last git annotated tag'
|
||||
version = new_version.replace('-', '.')
|
||||
return version
|
||||
if p.returncode == 0:
|
||||
return result.split()[0][1:].replace('-', '.')
|
||||
else:
|
||||
return '0.0.0-%s' % len(
|
||||
subprocess.check_output(
|
||||
['git', 'rev-list', 'HEAD']).splitlines())
|
||||
return '0.0.0'
|
||||
|
||||
|
||||
setup(name="fargo",
|
||||
|
@ -84,9 +87,14 @@ setup(name="fargo",
|
|||
include_package_data=True,
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'django>=1.7',
|
||||
'django-tables2',
|
||||
'django_sekizai',
|
||||
'gadjo',
|
||||
'XStatic',
|
||||
'XStatic_jQuery',
|
||||
],
|
||||
setup_requires=[
|
||||
'django>=1.7',
|
||||
],
|
||||
tests_require=[
|
||||
'nose>=0.11.4',
|
||||
|
|
Loading…
Reference in New Issue