Basic group profile and web/logo/description fields of the Group model
authorArthur Darcet <arthur.darcet@m4x.org>
Sat, 2 Feb 2013 14:34:25 +0000 (15:34 +0100)
committerArthur Darcet <arthur.darcet@m4x.org>
Sat, 2 Feb 2013 15:03:37 +0000 (16:03 +0100)
18 files changed:
.gitignore
requirements.txt
xnet/accounts/factories.py
xnet/accounts/models.py
xnet/accounts/urls.py [new file with mode: 0644]
xnet/accounts/views.py [new file with mode: 0644]
xnet/group/urls.py [deleted file]
xnet/group/views.py [deleted file]
xnet/settings.py
xnet/site/static/css/groups.css
xnet/templates/base.html
xnet/templates/groups/base.html [new file with mode: 0644]
xnet/templates/groups/index.html
xnet/templates/groups/view.html [new file with mode: 0644]
xnet/urls.py
xnet/utils/__init__.py [new file with mode: 0644]
xnet/utils/images/__init__.py [new file with mode: 0644]
xnet/utils/images/fields.py [new file with mode: 0644]

index f20b6f2..1f0afd1 100644 (file)
@@ -6,6 +6,7 @@
 # Private files
 xnet/db.sqlite
 xnet/private/
+xnet/media/
 
 # Build-related files
 doc/_build/
index 8c520f1..5203206 100644 (file)
@@ -1,4 +1,4 @@
-https://www.djangoproject.com/download/1.5c1/tarball/
+https://www.djangoproject.com/download/1.5c1/tarball/#egg=Django
 django_authgroupex
 django-xworkflows
 pil
index 4cb8b00..de56d20 100644 (file)
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+from django.contrib.webdesign import lorem_ipsum
 
 import factory
 
@@ -10,8 +11,10 @@ class XGroupFactory(factory.DjangoModelFactory):
 
     name = factory.Sequence(lambda n: u"Groupe n°%s" % n)
     short = factory.Sequence(lambda n: u"Groupe%s" % n)
-    kind = models.XGroup.KIND_GROUP
+    kind = factory.InfiniteIterator(k[0] for k in models.XGroup.KIND_CHOICES)
     domain = models.XGroup.DOMAIN_REGION
+    web = u"http://google.fr"
+    description = factory.LazyAttribute(lambda _: lorem_ipsum.paragraph())
 
 
 class AccountFactory(factory.DjangoModelFactory):
index 3e0f9c3..2d5ef42 100644 (file)
@@ -5,6 +5,8 @@ from django.db import models
 
 from django_xworkflows import models as xwf_models
 
+from xnet.utils.images.fields import ImageWithThumbsField as ImageField
+
 
 class MembershipWorkflow(xwf_models.Workflow):
     states = (
@@ -60,6 +62,11 @@ class XGroup(models.Model):
 
     dns = models.CharField(max_length=128, verbose_name=u"dns domain", blank=True)
 
+    web = models.CharField(max_length=255, verbose_name=u"website", blank=True)
+    #contact = models.ForeignKey('')
+    description = models.TextField()
+    logo = ImageField(upload_to='logos', null=True)
+
     class Meta:
         verbose_name = u"groupe"
         verbose_name_plural = u"groupes"
diff --git a/xnet/accounts/urls.py b/xnet/accounts/urls.py
new file mode 100644 (file)
index 0000000..c50b6d1
--- /dev/null
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import patterns, url
+
+
+urlpatterns = patterns(
+    'xnet.accounts.views',
+    url(r'^$', 'index', name='group-list'),
+    url(r'^view/([0-9]+)$', 'view', name='group-view'),
+)
diff --git a/xnet/accounts/views.py b/xnet/accounts/views.py
new file mode 100644 (file)
index 0000000..358de78
--- /dev/null
@@ -0,0 +1,11 @@
+from django.contrib import messages
+from django.shortcuts import get_object_or_404, render
+
+from xnet.accounts.models import XGroup
+
+
+def index(request):
+    return render(request, 'groups/index.html', {'groups': XGroup.objects.order_by('kind')})
+
+def view(request, pk):
+    return render(request, 'groups/view.html', {'group': get_object_or_404(XGroup, pk=pk)})
diff --git a/xnet/group/urls.py b/xnet/group/urls.py
deleted file mode 100644 (file)
index 5a16503..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-
-
-urlpatterns = patterns(
-    'xnet.group.views',
-    url(r'^$', 'index', name='index'),
-    url(r'^view/([0-9]+)$', 'view', name='view'),
-)
diff --git a/xnet/group/views.py b/xnet/group/views.py
deleted file mode 100644 (file)
index 8cc4072..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.contrib import messages
-from django.shortcuts import render
-
-from xnet.group.models import Group
-
-
-def index(request):
-    return render(request, 'groups/index.html', {'groups': Group.objects.order_by('kind')})
-
-def view(request):
-    pass
index 57eb721..595dd99 100644 (file)
@@ -65,12 +65,12 @@ USE_TZ = True
 
 # Absolute filesystem path to the directory that will hold user-uploaded files.
 # Example: "/home/media/media.lawrence.com/media/"
-MEDIA_ROOT = ''
+MEDIA_ROOT = os.path.join(ROOT_DIR, 'media')
 
 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
 # trailing slash.
 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
-MEDIA_URL = ''
+MEDIA_URL = '/media/'
 
 # Absolute path to the directory static files should be collected to.
 # Don't put anything in this directory yourself; store your static files
index 5ca99d8..db2d9a7 100644 (file)
 .group_list li {
     list-style: none;
 }
+
+.group-desc .logo {
+    float: right;
+    margin: 0;
+}
+.group-desc p {
+    float: left;
+    clear: left;
+}
index 69aa45d..0c28e53 100644 (file)
@@ -29,7 +29,7 @@
                 <div class="nav-collapse collapse">
                     <ul class="nav">
                         <li{% if top == 'groups' %} class="active"{% endif %}>
-                            <a href="/groups">
+                            <a href="{% url 'accounts:group-list' %}">
                                 {% trans "Groupes" %}
                             </a>
                         </li>
diff --git a/xnet/templates/groups/base.html b/xnet/templates/groups/base.html
new file mode 100644 (file)
index 0000000..793fd09
--- /dev/null
@@ -0,0 +1,7 @@
+{% extends "base.html" %}
+{% load static %}
+
+{% block css %}
+    {{ block.super }}
+    <link href="{% static 'css/groups.css' %}" rel="stylesheet">
+{% endblock %}
index 9001ee8..c6390fb 100644 (file)
@@ -1,10 +1,5 @@
-{% extends "base.html" %}
-{% load static %}
+{% extends "groups/base.html" %}
 
-{% block css %}
-    {{ block.super }}
-    <link href="{% static 'css/groups.css' %}" rel="stylesheet">
-{% endblock %}
 {% block js %}
     <script type="text/javascript">
         $('.search-field').keyup(function(){
@@ -29,7 +24,7 @@
     <input type="text" class="search-field" data-target="{{ group.grouper }}_type" />
     <ul>
         {% for item in group.list %}
-          <li><a href="{% url 'groups:view' item.pk %}">{{ item }}</a></li>
+          <li><a href="{% url 'accounts:group-view' item.pk %}">{{ item }}</a></li>
         {% endfor %}
     </ul>
 </div>
diff --git a/xnet/templates/groups/view.html b/xnet/templates/groups/view.html
new file mode 100644 (file)
index 0000000..d15a214
--- /dev/null
@@ -0,0 +1,14 @@
+{% extends "groups/base.html" %}
+{% block js %}
+    <script type="text/javascript">
+    </script>
+{% endblock %}
+
+{% block content %}
+<div class="group-desc">
+    <h1>{{ group.name }}</h1>
+    {% if group.logo %}<img class="logo" src="{{ group.logo.url_70x70 }}" />{% endif %}
+    {% if group.web %}<p><span>Web</span> : <a href="{{ group.web }}">{{ group.web }}</a></p>{% endif %}
+    {% if group.description %}<p>{{ group.description }}</p>{% endif %}
+</div>
+{% endblock %}
index 8c4236e..fe0b216 100644 (file)
@@ -1,3 +1,4 @@
+from django.conf import settings
 from django.conf.urls import patterns, include, url
 
 import django_authgroupex
@@ -9,8 +10,9 @@ admin.autodiscover()
 admin.site.login_template = 'xnet/admin_login.html'
 
 urlpatterns = patterns('',
+    url(r'media/(?P<path>.*)', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),
     url(r'^$', 'xnet.site.views.home', name='home'),
     url(r'^xorgauth/', authgroupex_view.login_view, name='xorgauth'),
-    url(r'^groups/', include('xnet.group.urls', namespace='groups')),
+    url(r'^groups/', include('xnet.accounts.urls', namespace='accounts')),
     url(r'^admin/', include(admin.site.urls)),
 )
diff --git a/xnet/utils/__init__.py b/xnet/utils/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xnet/utils/images/__init__.py b/xnet/utils/images/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xnet/utils/images/fields.py b/xnet/utils/images/fields.py
new file mode 100644 (file)
index 0000000..4ff7d35
--- /dev/null
@@ -0,0 +1,116 @@
+# -*- encoding: utf-8 -*-
+"""
+django-thumbs by Antonio Melé
+Modified by Arthur Darcet to support on demand resizing
+"""
+from django.db.models import ImageField
+from django.db.models.fields.files import ImageFieldFile
+from PIL import Image
+from django.core.files.base import ContentFile
+import cStringIO, re
+
+
+def generate_thumb(img, thumb_size, format):
+    img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
+    image = Image.open(img)
+    if image.mode not in ('L', 'RGB'):
+        image = image.convert('RGB')
+    thumb_w, thumb_h = thumb_size
+    if thumb_w == thumb_h and False:
+        xsize, ysize = image.size
+        minsize = min(xsize,ysize)
+        xnewsize = (xsize-minsize)/2
+        ynewsize = (ysize-minsize)/2
+        image2 = image.crop((xnewsize, ynewsize, xsize-xnewsize, ysize-ynewsize))
+        image2.load()
+        image2.thumbnail(thumb_size, Image.ANTIALIAS)
+    else:
+        image2 = image
+        image2.thumbnail(thumb_size, Image.ANTIALIAS)
+    io = cStringIO.StringIO()
+    if format.upper()=='JPG':
+        format = 'JPEG'
+    image2.save(io, format)
+    return ContentFile(io.getvalue())
+
+
+class ImageWithThumbsFieldFile(ImageFieldFile):
+    @staticmethod
+    def _get_for_size(s, (w,h)):
+        split = s.rsplit('.',1)
+        return u'{}.{}x{}.{}'.format(split[0],w,h,split[1])
+
+    def __getattr__(self, attr):
+        s = attr.split('url_')
+        if len(s) != 2 or 'x' not in s[1]:
+            raise AttributeError
+        if not self.name:
+            raise AttributeError('The picture has no file associated with it.')
+        size = map(int, s[1].split('x',1))
+        name = ImageWithThumbsFieldFile._get_for_size(self.name, size)
+        url = ImageWithThumbsFieldFile._get_for_size(self.url, size)
+        if not self.storage.exists(name):
+            thumb_content = generate_thumb(self, size, name.rsplit('.',1)[1])
+            self.storage.save(name, thumb_content)
+        return url
+
+    def delete(self, save=True):
+        directory, name = self.name.rsplit('/', 1)
+        _, files = self.storage.listdir(directory)
+        reg = re.compile(r'{}.[0-9]+x[0-9]+.{}'.format(*name.rsplit('.',1)))
+        for f in files:
+            if reg.match(f):
+                self.storage.delete(u'{}/{}'.format(directory, f))
+        super(ImageWithThumbsFieldFile, self).delete(save)
+
+
+class ImageWithThumbsField(ImageField):
+    attr_class = ImageWithThumbsFieldFile
+    """
+    Usage example:
+    ==============
+    photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
+
+    To retrieve image URL, exactly the same way as with ImageField:
+        my_object.photo.url
+    To retrieve thumbnails URL's just add the size to it:
+        my_object.photo.url_125x125
+        my_object.photo.url_300x200
+
+    Note: The 'sizes' attribute is not required. If you don't provide it,
+    ImageWithThumbsField will act as a normal ImageField
+
+    How it works:
+    =============
+    For each size in the 'sizes' atribute of the field it generates a
+    thumbnail with that size and stores it following this format:
+
+    available_filename.[width]x[height].extension
+
+    Where 'available_filename' is the available filename returned by the storage
+    backend for saving the original file.
+
+    Following the usage example above: For storing a file called "photo.jpg" it saves:
+    photo.jpg          (original file)
+    photo.125x125.jpg  (first thumbnail)
+    photo.300x200.jpg  (second thumbnail)
+
+    With the default storage backend if photo.jpg already exists it will use these filenames:
+    photo_.jpg
+    photo_.125x125.jpg
+    photo_.300x200.jpg
+
+    Note: django-thumbs assumes that if filename "any_filename.jpg" is available
+    filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
+
+    To do:
+    ======
+    Add method to regenerate thubmnails
+
+    """
+    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
+        self.verbose_name=verbose_name
+        self.name=name
+        self.width_field=width_field
+        self.height_field=height_field
+        super(ImageField, self).__init__(**kwargs)