summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/TODO19
-rw-r--r--app/photos/admin.py5
-rw-r--r--app/photos/forms.py28
-rw-r--r--app/photos/migrations/0002_auto_20151216_1958.py26
-rw-r--r--app/photos/migrations/0003_luxgallery_caption_style.py20
-rw-r--r--app/photos/models.py22
-rw-r--r--app/photos/urls.py9
-rw-r--r--app/photos/views.py13
-rw-r--r--design/sass/_inbox.scss55
-rw-r--r--design/templates/admin/photos/luxgallery/change_form.html134
-rw-r--r--design/templates/archives/gallery_list.html57
-rw-r--r--design/templates/details/photo_gallery.html2
12 files changed, 370 insertions, 20 deletions
diff --git a/app/TODO b/app/TODO
index 2ddc873..9743b23 100644
--- a/app/TODO
+++ b/app/TODO
@@ -36,24 +36,17 @@ iron out templates and push first piece live.
# photos:
-Okay we're uploading/creating galleries/adding images
+Add map and location picker to upload form
-Then resize all images for responsive gallery.
+handle portrait orientation in resizing
-Then play with gallery templates and add a template selecter to form.
+finish builder
-like this full width design: http://www.photobyrichard.com/photobyrichard/page/2/
-
-Next step is to create private galleries and password protect them on the server at a common url. Flow will be something like:
+Then play with gallery templates
-galleries = PhotoGallery.object.filter(private=True)
-for gal in galleries:
- create at url like /photos/private/gallery-list and
- /photos/private/gallery-name
-
-Then just require a password to get to anything under /photos/private/
+like this full width design: http://www.photobyrichard.com/photobyrichard/page/2/
-Then change the default PhotoGallery manager to filter out anything marked private. Or make sure to write all those queries with .filter(private=False)
+port existing galleries to new structure
---
diff --git a/app/photos/admin.py b/app/photos/admin.py
index d8ad853..659dcb6 100644
--- a/app/photos/admin.py
+++ b/app/photos/admin.py
@@ -1,4 +1,5 @@
from django.contrib import admin
+from django import forms
from django.contrib.gis.admin import OSMGeoAdmin
from django.conf.urls import patterns
from django.conf.urls import url
@@ -9,10 +10,11 @@ from django.contrib.admin import helpers
from django.http import HttpResponseRedirect
-from .forms import UploadZipForm
+from .forms import UploadZipForm, GalleryForm
class LuxImageAdmin(OSMGeoAdmin):
+
list_display = ('pk', 'admin_thumbnail', 'pub_date',)
list_filter = ('pub_date',)
search_fields = ['title', 'caption']
@@ -31,6 +33,7 @@ admin.site.register(LuxImage, LuxImageAdmin)
class LuxGalleryAdmin(OSMGeoAdmin):
+ form = GalleryForm
list_display = ('title', 'location', 'pub_date')
list_filter = ('location',)
diff --git a/app/photos/forms.py b/app/photos/forms.py
index f12ce42..08927cd 100644
--- a/app/photos/forms.py
+++ b/app/photos/forms.py
@@ -13,6 +13,7 @@ except ImportError:
from django import forms
from django.utils.translation import ugettext_lazy as _
+from django.utils.safestring import mark_safe
from django.contrib import messages
from django.core.files.base import ContentFile
from django.contrib.admin import widgets
@@ -28,6 +29,26 @@ from .utils import resize_image
logger = logging.getLogger('photos.forms')
+class GalleryForm(forms.ModelForm):
+ class Meta:
+ model = LuxGallery
+ fields = '__all__'
+ widgets = {
+ 'images': forms.SelectMultiple,
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(GalleryForm, self).__init__(*args, **kwargs)
+
+ images = LuxImage.objects.all()[:100]
+ items = []
+ for image in images:
+ items.append(
+ (image.pk, mark_safe('%s' % image.get_image_name()))
+ )
+ self.fields['images'].choices = items
+ self.fields['images'].allow_tags = True
+
class UploadZipForm(forms.Form):
"""
Handles the uploading of a gallery of photos packed in a .zip file
@@ -129,7 +150,7 @@ class UploadZipForm(forms.Form):
contentfile = ContentFile(data)
image.image.save(filename, contentfile)
image.save()
- gallery.image.add(image)
+ gallery.images.add(image)
with exiftool.ExifTool() as et:
meta = et.get_metadata(image.image.path)
@@ -146,7 +167,10 @@ class UploadZipForm(forms.Form):
try:
image.exif_lens = meta["MakerNotes:LensType"]
except:
- pass
+ try:
+ image.exif_lens = meta["XMP:Lens"]
+ except:
+ pass
try:
image.point = Point(meta["XMP:GPSLongitude"], meta["XMP:GPSLatitude"], srid=4326)
try:
diff --git a/app/photos/migrations/0002_auto_20151216_1958.py b/app/photos/migrations/0002_auto_20151216_1958.py
new file mode 100644
index 0000000..2a2bffb
--- /dev/null
+++ b/app/photos/migrations/0002_auto_20151216_1958.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9 on 2015-12-16 19:58
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('photos', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='luxgallery',
+ old_name='image',
+ new_name='images',
+ ),
+ migrations.AlterField(
+ model_name='luxgallery',
+ name='thumb',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='gallery_thumb', to='photos.LuxImage'),
+ ),
+ ]
diff --git a/app/photos/migrations/0003_luxgallery_caption_style.py b/app/photos/migrations/0003_luxgallery_caption_style.py
new file mode 100644
index 0000000..b46f970
--- /dev/null
+++ b/app/photos/migrations/0003_luxgallery_caption_style.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9 on 2015-12-17 11:01
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('photos', '0002_auto_20151216_1958'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='luxgallery',
+ name='caption_style',
+ field=models.CharField(blank=True, max_length=400, null=True),
+ ),
+ ]
diff --git a/app/photos/models.py b/app/photos/models.py
index c2d77e4..50ae43b 100644
--- a/app/photos/models.py
+++ b/app/photos/models.py
@@ -43,7 +43,10 @@ class LuxImage(models.Model):
get_latest_by = 'pub_date'
def __str__(self):
- return "%s" % self.pk
+ if self.title:
+ return "%s" % self.title
+ else:
+ return "%s" % self.pk
def get_image_name(self):
return self.image.url.split("galleries/original/")[1]
@@ -80,12 +83,13 @@ class LuxGallery(models.Model):
title = models.CharField(blank=True, max_length=300)
description = models.TextField(blank=True, null=True)
slug = models.CharField(blank=True, max_length=300)
- thumb = models.CharField(blank=True, max_length=300)
- image = models.ManyToManyField(LuxImage)
+ thumb = models.ForeignKey(LuxImage, related_name="gallery_thumb", null=True, blank=True)
+ images = models.ManyToManyField(LuxImage)
pub_date = models.DateTimeField(null=True)
point = models.PointField(null=True, blank=True)
location = models.ForeignKey(Location, null=True, blank=True)
is_public = models.BooleanField(default=True)
+ caption_style = models.CharField(blank=True, null=True, max_length=400)
class Meta:
ordering = ('-pub_date', 'id')
@@ -104,6 +108,18 @@ class LuxGallery(models.Model):
else:
return "/photos/galleries/private/%s" % (self.slug)
+ def latitude(self):
+ return self.point.y
+
+ def longitude(self):
+ return self.point.x
+
+ def thumbs(self):
+ lst = [x.image.name for x in self.images.all()]
+ lst = ["<a href='/media/%s'>%s</a>" % (x, x.split('/')[-1]) for x in lst]
+ return ', '.join(item for item in lst if item)
+ thumbs.allow_tags = True
+
class Photo(models.Model):
description = models.TextField(blank=True, null=True)
diff --git a/app/photos/urls.py b/app/photos/urls.py
index 0c7b752..67375da 100644
--- a/app/photos/urls.py
+++ b/app/photos/urls.py
@@ -16,6 +16,15 @@ urlpatterns = [
name="private"
),
url(
+ r'galleries/private/(?P<page>\d+)/$',
+ views.PrivateGalleryList.as_view(),
+ name="private_list"
+ ),
+ url(
+ r'galleries/private/$',
+ RedirectView.as_view(url="/photos/galleries/private/1/", permanent=False)
+ ),
+ url(
r'galleries/(?P<slug>[-\w]+)/$',
views.gallery,
),
diff --git a/app/photos/views.py b/app/photos/views.py
index 970bbd9..4b89e4c 100644
--- a/app/photos/views.py
+++ b/app/photos/views.py
@@ -10,12 +10,25 @@ from utils.views import PaginatedListView
from django.views.generic import ListView
from django.views.generic.detail import DetailView
+
class PrivateGallery(DetailView):
model = LuxGallery
slug_field = "slug"
template_name = "details/photo_gallery.html"
+class PrivateGalleryList(PaginatedListView):
+ template_name = 'archives/gallery_list.html'
+
+ def get_queryset(self):
+ return LuxGallery.objects.filter(is_public=False)
+
+ def get_context_data(self, **kwargs):
+ # Call the base implementation first to get a context
+ context = super(PrivateGalleryList, self).get_context_data(**kwargs)
+ context['is_private'] = True
+ return context
+
def gallery_list(request, page):
request.page_url = '/photos/%d/'
request.page = int(page)
diff --git a/design/sass/_inbox.scss b/design/sass/_inbox.scss
index 7f44753..f74fc31 100644
--- a/design/sass/_inbox.scss
+++ b/design/sass/_inbox.scss
@@ -13,3 +13,58 @@
margin-top: 3em;
@include constrain_wide;
}
+
+.photo-gallery {
+ margin: 0 auto;
+ margin-top: 1.5em;
+ position: relative;
+ width: 100%; /* for IE 6 */
+ img {
+ width: 100%;
+ max-width: 100%;
+ padding: 0;
+ }
+ h4 {
+ font-weight: 500;
+ margin: .5em 0;
+ letter-spacing: 1px;
+ font-size: 2em;
+ }
+ time { display: inline; }
+ a { color: inherit;}
+ @include breakpoint(alpha) {
+ div {
+ position: absolute;
+ bottom: 28%;
+ left: .35em;
+ text-align: left;
+ }
+ time {
+ margin-left: .5em;
+ }
+ h4 {
+ font-size: 2.75em;
+ margin-bottom: 0;
+ }
+ p {
+ line-height: 1.3;
+ max-width: 80%;
+ margin-left: .25em;
+ @include fontsize(18);
+ }
+ }
+ @include breakpoint(beta) {
+ div {
+ bottom: 22%;
+ }
+ }
+ @include breakpoint(gamma) {
+ div {
+ bottom: 20%;
+ }
+ p {
+ max-width: 40%;
+ }
+ @include constrain_wide;
+ }
+}
diff --git a/design/templates/admin/photos/luxgallery/change_form.html b/design/templates/admin/photos/luxgallery/change_form.html
new file mode 100644
index 0000000..448d415
--- /dev/null
+++ b/design/templates/admin/photos/luxgallery/change_form.html
@@ -0,0 +1,134 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_urls admin_static admin_modify %}
+
+{% block extrahead %}{{ block.super }}
+<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
+{{ media }}
+<script>
+if (!$) {
+ $ = django.jQuery;
+}
+$(function(){
+ $('#id_images').css('width', '400px').css('height', '400px');
+ $('#id_images option').each(function(){
+ $(this).attr('style', 'background: url({{MEDIA_URL}}images/galleries/thumb/'+$(this).text()+') no-repeat; background-size: 70px 70px; height: 80px; padding-left: 80px; line-height: 80px;');
+ });
+});
+</script>
+
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
+
+{% block coltype %}colM{% endblock %}
+
+{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
+
+{% if not is_popup %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
+&rsaquo; {% if has_change_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
+&rsaquo; {% if add %}{% trans 'Add' %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
+</div>
+{% endblock %}
+{% endif %}
+
+{% block content %}<div id="content-main">
+{% block object-tools %}
+{% if change %}{% if not is_popup %}
+ <ul class="object-tools">
+ {% block object-tools-items %}
+ <li>
+ {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
+ <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
+ </li>
+ {% if has_absolute_url %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif %}
+ {% endblock %}
+ </ul>
+{% endif %}{% endif %}
+{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form" novalidate>{% csrf_token %}{% block form_top %}{% endblock %}
+<div>
+{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1" />{% endif %}
+{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}" />{% endif %}
+{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
+{% if errors %}
+ <p class="errornote">
+ {% if errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
+ </p>
+ {{ adminform.form.non_field_errors }}
+{% endif %}
+
+{% block field_sets %}
+{% for fieldset in adminform %}
+ {% include "admin/includes/fieldset.html" %}
+{% endfor %}
+{% endblock %}
+
+{% block after_field_sets %}{% endblock %}
+
+{% block inline_field_sets %}
+{% for inline_admin_formset in inline_admin_formsets %}
+ {% include inline_admin_formset.opts.template %}
+{% endfor %}
+{% endblock %}
+
+{% block after_related_objects %}{% endblock %}
+
+{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
+
+{% block admin_change_form_document_ready %}
+ <script type="text/javascript">
+ (function($) {
+ $(document).ready(function() {
+ $('.add-another').click(function(e) {
+ e.preventDefault();
+ var event = $.Event('django:add-another-related');
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ showAddAnotherPopup(this);
+ }
+ });
+ $('.related-lookup').click(function(e) {
+ e.preventDefault();
+ var event = $.Event('django:lookup-related');
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ showRelatedObjectLookupPopup(this);
+ }
+ });
+ $('body').on('click', '.related-widget-wrapper-link', function(e) {
+ e.preventDefault();
+ if (this.href) {
+ var event = $.Event('django:show-related', {href: this.href});
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ showRelatedObjectPopup(this);
+ }
+ }
+ });
+ $('body').on('change', '.related-widget-wrapper select', function(e) {
+ var event = $.Event('django:update-related');
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ updateRelatedObjectLinks(this);
+ }
+ });
+ $('.related-widget-wrapper select').trigger('change');
+
+ {% if adminform and add %}
+ $('form#{{ opts.model_name }}_form :input:visible:enabled:first').focus()
+ {% endif %}
+ });
+ })(django.jQuery);
+ </script>
+{% endblock %}
+
+{# JavaScript for prepopulated fields #}
+{% prepopulated_fields_js %}
+
+</div>
+</form></div>
+{% endblock %}
diff --git a/design/templates/archives/gallery_list.html b/design/templates/archives/gallery_list.html
new file mode 100644
index 0000000..1ae8696
--- /dev/null
+++ b/design/templates/archives/gallery_list.html
@@ -0,0 +1,57 @@
+{% extends 'base.html' %}
+{% load get_image_size %}
+{% load typogrify_tags %}
+{% load pagination_tags %}
+
+{% block pagetitle %}Luxagraf | {% if region %}Photo Galleries: Images from {{region.name|title|smartypants|safe}}{%else%}Photo Galleries: Images from Around the World {%endif%} Page {{page}}{% endblock %}
+{% block metadescription %}{% if region %}Photo Galleries from {{region.name|title|smartypants|safe}}{%else%}Photo Galleries: Images from Around the World {%endif%} Page {{page}}{% endblock %}
+
+{%block extrahead%}
+<style>
+ @media screen and (min-width: 38em){
+ {% autopaginate object_list 24 %} {% for object in object_list %}
+ {%if object.caption_style%}
+#image-{{forloop.counter}} .gallery-text {
+ {{object.caption_style}}
+}
+ {%endif%}
+ {%endfor%}
+</style>
+{%endblock%}
+
+{%block bodyid%}id="photo-archive"{%endblock%}
+
+{% block primary %}<ul class="bl" id="breadcrumbs" itemscope itemtype="http://data-vocabulary.org/Breadcrumb">
+ <li><a href="/" title="luxagraf homepage" itemprop="url"><span itemprop="title">Home</span></a> &rarr;</li>
+ {% if region %}{%if region.name == 'United States'%} <li><a href="/photos/" title="See all Photos" itemprop="url"><span itemprop="title">Photos</span></a> &rarr;</li>
+ <li>the United States</li>{%else%}<li><a href="/photos/" title="See all Photos" itemprop="url"><span itemprop="title">Photos</span></a> &rarr;</li> <li>{{region.name|title|smartypants|safe}}{%endif%}{%else%}<li>{% if is_private %}<a href="/photos/">Photos</a> &rarr;</li>{%else%}<li>Photos </li>{%endif%}{%endif%}
+ {% if is_private %}<li>Private</li>{%endif%}
+ </ul>
+ <main role="main">
+ <h1 class="hide">{% if region %}Photographs from {{region.name|title|smartypants|safe}}{%else%}Photographs from Around the World {%endif%}</h1>
+ {% autopaginate object_list 24 %}{% for object in object_list %}
+ <article id="image-{{forloop.counter}}" class="photo-gallery">
+ <a href="{{object.get_absolute_url}}" title="view images from {{ object.title }}">
+ <img sizes="(max-width: 1140px) 100vw"
+ srcset="{% get_image_size object.thumb 'small' %} 720w,
+ {% get_image_size object.thumb 'medium' %} 1140w,
+ {% get_image_size object.thumb 'large' %} 2280w,"
+ src="{% get_image_size object.thumb 'medium' %}" alt="{{object.title}}" ></a>
+ <div class="gallery-text">
+ <h4>{{object.title}}</h4>
+ <time class="dt-published published dt-updated post--date" datetime="{{object.pub_date|date:'c'}}">{{object.pub_date|date:"F"}} <span>{{object.pub_date|date:"j, Y"}}</span></time> &ndash;
+ <span class="p-location h-adr adr post--location" itemprop="contentLocation" itemscope itemtype="http://schema.org/Place">
+ {% if object.location.state.country.name == "United States" %}<span class="p-locality locality">{{object.location.name|smartypants|safe}}</span>, <a class="p-region region" href="/photos/united-states/" title="all galleries from the United States">{{object.location.state.name}}</a>, <span class="p-country-name">U.S.</span>{%else%}<span class="p-region">{{object.location.name|smartypants|safe}}</span>, <a class="p-country-name country-name" href="/jrnl/{{object.location.state.country.slug}}/" title="travel writing from {{object.location.state.country.name}}">{{object.state.country.name}}</a>{%endif%}
+ <span style="display: none;" itemprop="geo" itemscope itemtype="http://schema.org/GeoCoordinates">
+ <data itemprop="latitude" class="p-latitude" value="{{object.latitude}}">{{object.latitude}}</data>
+ <data itemprop="longitude" class="p-longitude" value="{{object.longitude}}">{{object.longitude}}</data>
+ </span>
+ </span>
+ {%if object.description%}<p>{{object.description|safe|smartypants|widont}}</p>{%endif%}
+ </div>
+ </article>{% endfor %}
+ </ul>
+ </main>
+ <nav class="pagination">{% paginate %}
+ </nav>
+{% endblock %}
diff --git a/design/templates/details/photo_gallery.html b/design/templates/details/photo_gallery.html
index b991ba2..d34f4e4 100644
--- a/design/templates/details/photo_gallery.html
+++ b/design/templates/details/photo_gallery.html
@@ -24,7 +24,7 @@
</ul>
<p class="directions">Use left/right arrow keys to navigate through photos</p>
<main id="slides" class="image-gallery--wrapper">
- <h1 class="hide">Photos from {{object.title}}</h1>{%for photo in object.image.all reversed %}
+ <h1 class="hide">Photos from {{object.title}}</h1>{%for photo in object.images.all reversed %}
<article id="image-{{forloop.counter}}">
<h6><a href="#image-{{forloop.counter}}" class="permalink" title="link to this image">&#8734; {{forloop.counter|number_to_word}} &#8734;</a></h6>
<figure class="fig">