summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorluxagraf <sng@luxagraf.net>2018-01-28 10:30:57 -0600
committerluxagraf <sng@luxagraf.net>2018-01-28 10:30:57 -0600
commit916474036c1d2e041ed6425a3c0370a2e4c7e7bc (patch)
tree0536598b9a114303102f6f66242fb31d51beea36
parenta278fc64c1dcd5321492a7dc384305b8719cb85e (diff)
abstracted birds out to sightings
-rw-r--r--app/birds/admin.py52
-rw-r--r--app/birds/models.py149
-rw-r--r--app/jrnl/views.py10
-rw-r--r--app/sightings/__init__.py0
-rw-r--r--app/sightings/admin.py49
-rw-r--r--app/sightings/autocomplete_light_registry.py24
-rw-r--r--app/sightings/build.py21
-rw-r--r--app/sightings/migrations/0001_initial.py72
-rw-r--r--app/sightings/migrations/__init__.py0
-rw-r--r--app/sightings/models.py175
-rw-r--r--app/sightings/urls.py34
-rw-r--r--app/sightings/views.py48
-rw-r--r--config/base_urls.py1
-rw-r--r--design/sass/_writing_details.scss4
-rw-r--r--design/templates/admin/index.html2
-rw-r--r--design/templates/archives/sightings.html56
-rw-r--r--design/templates/details/entry.html6
-rw-r--r--design/templates/details/sighting.html56
18 files changed, 744 insertions, 15 deletions
diff --git a/app/birds/admin.py b/app/birds/admin.py
index 77ebc8a..50bd755 100644
--- a/app/birds/admin.py
+++ b/app/birds/admin.py
@@ -1,12 +1,53 @@
from django.contrib import admin
from django.contrib.gis.admin import OSMGeoAdmin
-from birds.models import BirdSighting, BirdAudio, BirdClass, Bird
+from birds.models import BirdSighting, BirdAudio, BirdClass, Bird, APClass, AP, Sighting
from photos.forms import GalleryForm
from utils.util import get_latlon
from utils.widgets import CustomSelectMultiple
+class GalleryFormPlus(GalleryForm):
+ def __init__(self, *args, **kwargs):
+ super(GalleryFormPlus, self).__init__(*args, **kwargs)
+ self.base_fields['seen_by'].widget = CustomSelectMultiple()
+
+ class Meta:
+ model = Sighting
+ fields = '__all__'
+
+
+@admin.register(APClass)
+class APClassAdmin(admin.ModelAdmin):
+ list_display = ('common_name', 'scientific_name', 'kind')
+ list_filter = ('kind',)
+
+
+@admin.register(AP)
+class APAdmin(admin.ModelAdmin):
+ list_display = ('pk', 'common_name', 'scientific_name', 'kind', 'code', 'apclass')
+ list_filter = ('apclass__kind','apclass')
+
+
+@admin.register(Sighting)
+class SightingAdmin(OSMGeoAdmin):
+ form = GalleryFormPlus
+ list_filter = ('seen_by',('location', admin.RelatedOnlyFieldListFilter),)
+ list_display = ('ap', 'location')
+ # options for OSM map Using custom ESRI topo map
+ lat, lon = get_latlon()
+ print(lat, lon)
+ default_lon = lon
+ default_lat = lat
+ default_zoom = 13
+ units = True
+ scrollable = False
+ map_width = 700
+ map_height = 425
+ map_template = 'gis/admin/osm.html'
+ openlayers_url = '/static/admin/js/OpenLayers.js'
+
+
class BirdClassAdmin(admin.ModelAdmin):
list_display = ('common_name', 'scientific_name',)
@@ -19,15 +60,6 @@ class BirdAdmin(admin.ModelAdmin):
list_display = ('pk', 'common_name', 'scientific_name', 'code', 'bird_class')
list_filter = ('bird_class',)
-class GalleryFormPlus(GalleryForm):
- def __init__(self, *args, **kwargs):
- super(GalleryFormPlus, self).__init__(*args, **kwargs)
- self.base_fields['seen_by'].widget = CustomSelectMultiple()
-
- class Meta:
- model = BirdSighting
- fields = '__all__'
-
class BirdSightingAdmin(OSMGeoAdmin):
form = GalleryFormPlus
list_filter = (
diff --git a/app/birds/models.py b/app/birds/models.py
index c73e7d7..5a14786 100644
--- a/app/birds/models.py
+++ b/app/birds/models.py
@@ -17,6 +17,7 @@ def get_upload_path(self, filename):
# from http://aba.org/checklist/codes.html
ABA_CODES = (
+ (0, 'unknown'),
(1, 'regular occurring - common'),
(2, 'regular occurring - less common'),
(3, 'rare'),
@@ -25,6 +26,154 @@ ABA_CODES = (
(6, 'Cannot be found'),
)
+KIND_LIST = (
+ (1, 'Bird'),
+ (2, 'Mammal'),
+ (3, 'Reptile'),
+ (4, 'Amphibian'),
+ (5, 'Plant'),
+)
+
+
+class APClass(models.Model):
+ common_name = models.CharField(max_length=200)
+ scientific_name = models.CharField(max_length=200)
+ kind = models.IntegerField(choices=KIND_LIST, default=1)
+
+ class Meta:
+ verbose_name_plural = 'Animal/Plant Class'
+ ordering = ["kind", "common_name"]
+
+ def __str__(self):
+ return self.common_name
+
+class AP(models.Model):
+ common_name = models.CharField(max_length=200)
+ slug = models.SlugField()
+ scientific_name = models.CharField(max_length=200)
+ code = models.IntegerField(choices=ABA_CODES, default=0)
+ apclass = models.ForeignKey(APClass, on_delete=models.CASCADE)
+ image = models.FileField(upload_to=get_upload_path, null=True, blank=True, help_text="width of high res is 1360px")
+ image_credit = models.CharField(max_length=200, blank=True, null=True)
+
+ def __str__(self):
+ return self.common_name
+
+ def get_image_url(self):
+ return "%s%s" % (settings.IMAGES_URL, self.image.url.split("media")[1][8:])
+
+ def get_absolute_url(self):
+ return reverse("sightings:detail", kwargs={"slug": self.slug})
+
+ def kind(self):
+ return self.apclass.kind
+
+ class Meta:
+ verbose_name_plural = 'Animal/Plant'
+ verbose_name = 'Animal/Plant'
+ ordering = ["common_name", ]
+
+ def save(self, *args, **kwargs):
+ self.slug = slugify(self.common_name[:50])
+ super(AP, self).save(*args, **kwargs)
+
+
+class Sighting(models.Model):
+ ap = models.ForeignKey(AP, on_delete=models.CASCADE)
+ point = models.PointField(blank=True)
+ location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, related_name="location_old")
+ date = models.DateTimeField('Date', default=timezone.now)
+ seen_by = models.ManyToManyField(User, related_name="seenby_old")
+ images = models.ManyToManyField(LuxImage, blank=True, related_name="images_old")
+ #audio = models.ManyToManyField(BirdAudio, blank=True)
+
+ class Meta:
+ ordering = ["-date", ]
+
+ @property
+ def state(self):
+ return self.location.state
+
+ @property
+ def country(self):
+ return self.location.state.country
+
+ @property
+ def region(self):
+ return self.location.state.country.lux_region
+
+ @property
+ def longitude(self):
+ '''Get the site's longitude.'''
+ return self.point.x
+
+ @property
+ def latitude(self):
+ '''Get the site's latitude.'''
+ return self.point.y
+
+ def get_small_image(self):
+ for img in self.images.all():
+ for size in img.sizes.all():
+ if size.width > 360 and size.width < 700:
+ return img.get_image_by_size(size)
+
+ def get_absolute_url(self):
+ return reverse("birds:detail", kwargs={"slug": self.bird.slug})
+
+ def __str__(self):
+ return self.ap.common_name
+
+ def save(self):
+ if not self.point:
+ self.point = Sighting.objects.latest().point
+ try:
+ self.location = Location.objects.filter(
+ geometry__contains=self.point
+ ).get()
+ except Location.DoesNotExist:
+ raise forms.ValidationError("There is no location associated with that point, add it: %sadmin/locations/location/add/" % (settings.BASE_URL))
+ super(Sighting, self).save()
+
+"""
+Migration from Birds to abstract:
+birdclass = BirdClass.objects.all()
+for b in birdclass:
+ APClass.objects.create(
+ common_name = b.common_name,
+ scientific_name = b.scientific_name,
+ kind = 1
+ )
+
+birds = Bird.objects.all()
+for b in birds:
+ ap = APClass.objects.get(scientific_name=b.bird_class.scientific_name)
+ print(ap)
+ AP.objects.create(
+ common_name = b.common_name,
+ scientific_name = b.scientific_name,
+ code = b.code,
+ apclass = ap,
+ image = b.image,
+ image_credit = b.image_credit,
+ )
+ print(t)
+
+birdsighting = BirdSighting.objects.all()
+for bird in birdsighting:
+ ap = AP.objects.get(scientific_name=bird.bird.scientific_name)
+ s = Sighting.objects.create(
+ ap = ap,
+ point = bird.point,
+ location = bird.location,
+ date = bird.date,
+ )
+ for t in bird.images.all():
+ s.images.add(t)
+ for t in bird.seen_by.all():
+ s.seen_by.add(t)
+"""
+
class BirdClass(models.Model):
common_name = models.CharField(max_length=200)
diff --git a/app/jrnl/views.py b/app/jrnl/views.py
index 72a64d7..253aa5e 100644
--- a/app/jrnl/views.py
+++ b/app/jrnl/views.py
@@ -8,9 +8,8 @@ from django.conf import settings
from utils.views import PaginatedListView
from .models import Entry, HomepageCurrator
-from locations.models import CheckIn
-
-from locations.models import Country, Region
+from locations.models import CheckIn, Country, Region
+from birds.models import BirdSighting
class EntryList(PaginatedListView):
@@ -81,6 +80,11 @@ class EntryDetailView(DetailView):
)
return obj
+ def get_context_data(self, **kwargs):
+ context = super(EntryDetailView, self).get_context_data(**kwargs)
+ context['wildlife'] = BirdSighting.objects.filter(location=self.get_object().location)
+ return context
+
class EntryDetailViewTXT(EntryDetailView):
template_name = "details/entry.txt"
diff --git a/app/sightings/__init__.py b/app/sightings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/sightings/__init__.py
diff --git a/app/sightings/admin.py b/app/sightings/admin.py
new file mode 100644
index 0000000..196712a
--- /dev/null
+++ b/app/sightings/admin.py
@@ -0,0 +1,49 @@
+from django.contrib import admin
+from django.contrib.gis.admin import OSMGeoAdmin
+from .models import APClass, AP, Sighting
+
+from photos.forms import GalleryForm
+from utils.util import get_latlon
+from utils.widgets import CustomSelectMultiple
+
+
+class GalleryFormPlus(GalleryForm):
+ def __init__(self, *args, **kwargs):
+ super(GalleryFormPlus, self).__init__(*args, **kwargs)
+ self.base_fields['seen_by'].widget = CustomSelectMultiple()
+
+ class Meta:
+ model = Sighting
+ fields = '__all__'
+
+
+@admin.register(APClass)
+class APClassAdmin(admin.ModelAdmin):
+ list_display = ('common_name', 'scientific_name', 'kind')
+ list_filter = ('kind',)
+
+
+@admin.register(AP)
+class APAdmin(admin.ModelAdmin):
+ list_display = ('pk', 'common_name', 'scientific_name', 'kind', 'code', 'apclass')
+ list_filter = ('apclass__kind','apclass')
+ search_fields = ['common_name', 'scientific_name']
+
+
+@admin.register(Sighting)
+class SightingAdmin(OSMGeoAdmin):
+ form = GalleryFormPlus
+ list_filter = ('seen_by',('location', admin.RelatedOnlyFieldListFilter),)
+ list_display = ('ap', 'location')
+ # options for OSM map Using custom ESRI topo map
+ lat, lon = get_latlon()
+ print(lat, lon)
+ default_lon = lon
+ default_lat = lat
+ default_zoom = 13
+ units = True
+ scrollable = False
+ map_width = 700
+ map_height = 425
+ map_template = 'gis/admin/osm.html'
+ openlayers_url = '/static/admin/js/OpenLayers.js'
diff --git a/app/sightings/autocomplete_light_registry.py b/app/sightings/autocomplete_light_registry.py
new file mode 100644
index 0000000..1cfa881
--- /dev/null
+++ b/app/sightings/autocomplete_light_registry.py
@@ -0,0 +1,24 @@
+import autocomplete_light.shortcuts as al
+from .models import Bird
+
+# This will generate a PersonAutocomplete class
+al.register(Bird,
+ # Just like in ModelAdmin.search_fields
+ search_fields=['common_name','scientific_name'],
+ attrs={
+ # This will set the input placeholder attribute:
+ 'placeholder': 'Tags...',
+ # This will set the yourlabs.Autocomplete.minimumCharacters
+ # options, the naming conversion is handled by jQuery
+ 'data-autocomplete-minimum-characters': 1,
+},
+ # This will set the data-widget-maximum-values attribute on the
+ # widget container element, and will be set to
+ # yourlabs.Widget.maximumValues (jQuery handles the naming
+ # conversion).
+ widget_attrs={
+ 'data-widget-maximum-values': 4,
+ # Enable modern-style widget !
+ 'class': 'modern-style',
+ },
+)
diff --git a/app/sightings/build.py b/app/sightings/build.py
new file mode 100644
index 0000000..565b675
--- /dev/null
+++ b/app/sightings/build.py
@@ -0,0 +1,21 @@
+import os
+from django.urls import reverse
+from builder.base import BuildNew
+
+
+class BuildBirds(BuildNew):
+
+ def build(self):
+ self.build_detail_view()
+ self.build_list_view(
+ base_path=reverse("birds:list_redirect"),
+ paginate_by=24
+ )
+ print("building birds")
+
+ def get_model_queryset(self):
+ return self.model.objects.all()
+
+def builder():
+ j = BuildBirds("birds", "birdsighting")
+ j.build()
diff --git a/app/sightings/migrations/0001_initial.py b/app/sightings/migrations/0001_initial.py
new file mode 100644
index 0000000..4c25926
--- /dev/null
+++ b/app/sightings/migrations/0001_initial.py
@@ -0,0 +1,72 @@
+# Generated by Django 2.0.1 on 2018-01-28 10:05
+
+from django.conf import settings
+import django.contrib.gis.db.models.fields
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import sightings.models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('photos', '0018_auto_20161130_1218'),
+ ('locations', '0002_checkin'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AP',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('common_name', models.CharField(max_length=200)),
+ ('slug', models.SlugField()),
+ ('scientific_name', models.CharField(max_length=200)),
+ ('code', models.IntegerField(choices=[(0, 'unknown'), (1, 'regular occurring - common'), (2, 'regular occurring - less common'), (3, 'rare'), (4, 'casual'), (5, 'accidental'), (6, 'Cannot be found')], default=0)),
+ ('image', models.FileField(blank=True, help_text='width of high res is 1360px', null=True, upload_to=sightings.models.get_upload_path)),
+ ('image_credit', models.CharField(blank=True, max_length=200, null=True)),
+ ],
+ options={
+ 'verbose_name': 'Animal/Plant',
+ 'verbose_name_plural': 'Animal/Plant',
+ 'ordering': ['common_name'],
+ },
+ ),
+ migrations.CreateModel(
+ name='APClass',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('common_name', models.CharField(max_length=200)),
+ ('scientific_name', models.CharField(max_length=200)),
+ ('kind', models.IntegerField(choices=[(1, 'Bird'), (2, 'Mammal'), (3, 'Reptile'), (4, 'Amphibian'), (5, 'Plant')], default=1)),
+ ],
+ options={
+ 'verbose_name_plural': 'Animal/Plant Class',
+ 'ordering': ['kind', 'common_name'],
+ },
+ ),
+ migrations.CreateModel(
+ name='Sighting',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('point', django.contrib.gis.db.models.fields.PointField(blank=True, srid=4326)),
+ ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date')),
+ ('ap', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sightings.AP')),
+ ('images', models.ManyToManyField(blank=True, to='photos.LuxImage')),
+ ('location', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='locations.Location')),
+ ('seen_by', models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'ordering': ['-date'],
+ },
+ ),
+ migrations.AddField(
+ model_name='ap',
+ name='apclass',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sightings.APClass'),
+ ),
+ ]
diff --git a/app/sightings/migrations/__init__.py b/app/sightings/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/sightings/migrations/__init__.py
diff --git a/app/sightings/models.py b/app/sightings/models.py
new file mode 100644
index 0000000..7f93fa8
--- /dev/null
+++ b/app/sightings/models.py
@@ -0,0 +1,175 @@
+import datetime
+from django.urls import reverse
+from django.template.defaultfilters import slugify
+from django.contrib.gis.db import models
+from django.contrib.auth.models import User
+from django.utils import timezone
+from locations.models import Location
+from django import forms
+from django.conf import settings
+
+from photos.models import LuxImage
+
+
+def get_upload_path(self, filename):
+ return "images/sightings-images/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename)
+
+
+# from http://aba.org/checklist/codes.html
+ABA_CODES = (
+ (0, 'unknown'),
+ (1, 'regular occurring - common'),
+ (2, 'regular occurring - less common'),
+ (3, 'rare'),
+ (4, 'casual'),
+ (5, 'accidental'),
+ (6, 'Cannot be found'),
+)
+
+KIND_LIST = (
+ (1, 'Bird'),
+ (2, 'Mammal'),
+ (3, 'Reptile'),
+ (4, 'Amphibian'),
+ (5, 'Plant'),
+)
+
+
+class APClass(models.Model):
+ common_name = models.CharField(max_length=200)
+ scientific_name = models.CharField(max_length=200)
+ kind = models.IntegerField(choices=KIND_LIST, default=1)
+
+ class Meta:
+ verbose_name_plural = 'Animal/Plant Class'
+ ordering = ["kind", "common_name"]
+
+ def __str__(self):
+ return self.common_name
+
+class AP(models.Model):
+ common_name = models.CharField(max_length=200)
+ slug = models.SlugField()
+ scientific_name = models.CharField(max_length=200)
+ code = models.IntegerField(choices=ABA_CODES, default=0)
+ apclass = models.ForeignKey(APClass, on_delete=models.CASCADE)
+ image = models.FileField(upload_to=get_upload_path, null=True, blank=True, help_text="width of high res is 1360px")
+ image_credit = models.CharField(max_length=200, blank=True, null=True)
+
+ def __str__(self):
+ return self.common_name
+
+ def get_image_url(self):
+ return "%s%s" % (settings.IMAGES_URL, self.image.url.split("media")[1][8:])
+
+ def get_absolute_url(self):
+ return reverse("sightings:detail", kwargs={"slug": self.slug})
+
+ def kind(self):
+ return self.apclass.kind
+
+ class Meta:
+ verbose_name_plural = 'Animal/Plant'
+ verbose_name = 'Animal/Plant'
+ ordering = ["common_name", ]
+
+ def save(self, *args, **kwargs):
+ self.slug = slugify(self.common_name[:50])
+ super(AP, self).save(*args, **kwargs)
+
+
+class Sighting(models.Model):
+ ap = models.ForeignKey(AP, on_delete=models.CASCADE)
+ point = models.PointField(blank=True)
+ location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True)
+ date = models.DateTimeField('Date', default=timezone.now)
+ seen_by = models.ManyToManyField(User)
+ images = models.ManyToManyField(LuxImage, blank=True)
+ #audio = models.ManyToManyField(BirdAudio, blank=True)
+
+ class Meta:
+ ordering = ["-date", ]
+
+ @property
+ def state(self):
+ return self.location.state
+
+ @property
+ def country(self):
+ return self.location.state.country
+
+ @property
+ def region(self):
+ return self.location.state.country.lux_region
+
+ @property
+ def longitude(self):
+ '''Get the site's longitude.'''
+ return self.point.x
+
+ @property
+ def latitude(self):
+ '''Get the site's latitude.'''
+ return self.point.y
+
+ def get_small_image(self):
+ for img in self.images.all():
+ for size in img.sizes.all():
+ if size.width > 360 and size.width < 700:
+ return img.get_image_by_size(size)
+
+ def get_absolute_url(self):
+ return reverse("sightings:detail", kwargs={"slug": self.bird.slug})
+
+ def __str__(self):
+ return self.ap.common_name
+
+ def save(self, *args, **kwargs):
+ if not self.point:
+ self.point = Sighting.objects.latest().point
+ try:
+ self.location = Location.objects.filter(
+ geometry__contains=self.point
+ ).get()
+ except Location.DoesNotExist:
+ raise forms.ValidationError("There is no location associated with that point, add it: %sadmin/locations/location/add/" % (settings.BASE_URL))
+ super(Sighting, self).save()
+
+"""
+Migration from Birds to abstract:
+apclass = OLDAPClass.objects.all()
+for b in apclass:
+ NewAPClass.objects.create(
+ common_name = b.common_name,
+ scientific_name = b.scientific_name,
+ kind = 1
+ )
+
+ap = OLDAP.objects.all()
+for b in ap:
+ apnew = NewAPClass.objects.get(scientific_name=b.apclass.scientific_name)
+ print(ap)
+ NewAP.objects.create(
+ common_name = b.common_name,
+ scientific_name = b.scientific_name,
+ code = b.code,
+ apclass = apnew,
+ image = b.image,
+ image_credit = b.image_credit,
+ )
+ print(t)
+
+oldsighting = OLDSighting.objects.all()
+for bird in oldsighting:
+ ap = NEWAP.objects.get(scientific_name=bird.ap.scientific_name)
+ s = NEWSighting.objects.create(
+ ap = ap,
+ point = bird.point,
+ location = bird.location,
+ date = bird.date,
+ )
+ for t in bird.images.all():
+ s.images.add(t)
+ for t in bird.seen_by.all():
+ s.seen_by.add(t)
+"""
diff --git a/app/sightings/urls.py b/app/sightings/urls.py
new file mode 100644
index 0000000..51b29d7
--- /dev/null
+++ b/app/sightings/urls.py
@@ -0,0 +1,34 @@
+from django.urls import path
+from . import views
+
+app_name = "sightings"
+
+urlpatterns = [
+ path(
+ r'',
+ views.SightingListView.as_view(),
+ {'page':1},
+ name="list"
+ ),
+ path(
+ r'<int:page>/',
+ views.SightingListView.as_view(),
+ name="list"
+ ),
+ path(
+ r'<str:slug>',
+ views.SightingDetailView.as_view(),
+ name='detail'
+ ),
+ path(
+ r'<str:user>/',
+ views.SightingListUserView.as_view(),
+ {'page':1},
+ name='list_by_person'
+ ),
+ path(
+ r'<str:user>/<int:page>/',
+ views.SightingListUserView.as_view(),
+ name='list_by_person'
+ ),
+]
diff --git a/app/sightings/views.py b/app/sightings/views.py
new file mode 100644
index 0000000..f23898c
--- /dev/null
+++ b/app/sightings/views.py
@@ -0,0 +1,48 @@
+from django.views.generic.detail import DetailView
+from django.contrib.auth.models import User
+from utils.views import PaginatedListView
+from .models import AP, Sighting
+
+class SightingListView(PaginatedListView):
+ template_name = 'archives/sightings.html'
+
+ def get_queryset(self):
+ return Sighting.objects.all()
+
+
+class SightingListUserView(PaginatedListView):
+ template_name = 'archives/sightings.html'
+
+ def get_queryset(self):
+ return Sighting.objects.filter(
+ seen_by__username=self.kwargs['user']
+ )
+
+ def get_context_data(self, **kwargs):
+ # Call the base implementation first to get a context
+ context = super(SightingListUserView, self).get_context_data(**kwargs)
+ context['user'] = User.objects.get(username=self.kwargs['user'])
+ return context
+
+
+class SightingDetailView(DetailView):
+ model = AP
+ template_name = "details/sighting.html"
+ slug_field = "slug"
+
+ def get_context_data(self, **kwargs):
+ # Call the base implementation first to get a context
+ context = super(SightingDetailView, self).get_context_data(**kwargs)
+ #try:
+ # context['recording'] = SightingAudio.objects.get(
+ # ap__slug=self.kwargs['slug']
+ # )
+ #except SightingAudio.DoesNotExist:
+ # pass
+ try:
+ context['sighting'] = Sighting.objects.filter(
+ ap__slug=self.kwargs['slug']
+ )
+ except Sighting.DoesNotExist:
+ pass
+ return context
diff --git a/config/base_urls.py b/config/base_urls.py
index 1a0ea83..d709f8c 100644
--- a/config/base_urls.py
+++ b/config/base_urls.py
@@ -46,6 +46,7 @@ urlpatterns = [
path(r'books/', include('books.urls')),
path(r'field-notes/', include('notes.urls')),
path(r'birds/', include('birds.urls', namespace='birds')),
+ path(r'sightings/', include('sightings.urls', namespace='sightings')),
path(r'src/', include('src.urls', namespace='src')),
path(r'figments/', include('figments.urls', namespace='figments')),
path(r'resume/', include('resume.urls', namespace='resume')),
diff --git a/design/sass/_writing_details.scss b/design/sass/_writing_details.scss
index 8e011e5..348d617 100644
--- a/design/sass/_writing_details.scss
+++ b/design/sass/_writing_details.scss
@@ -746,3 +746,7 @@ figure.picwide img.picwide {
float: none;
}
}
+.wildlife {
+ @include constrain_narrow;
+ text-align: left;
+}
diff --git a/design/templates/admin/index.html b/design/templates/admin/index.html
index 6642012..54f9798 100644
--- a/design/templates/admin/index.html
+++ b/design/templates/admin/index.html
@@ -25,9 +25,7 @@ td, th {
{% block content %}
<div id="content-main">
-{% load admin_reorder %}
{% if app_list %}
- {% app_order %}
<div class="module">
<table>
<caption><a href="{{ app.app_url }}" class="section" title="{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}">Frequently Used</a></caption>
diff --git a/design/templates/archives/sightings.html b/design/templates/archives/sightings.html
new file mode 100644
index 0000000..71cd389
--- /dev/null
+++ b/design/templates/archives/sightings.html
@@ -0,0 +1,56 @@
+{% extends 'base.html' %}
+{% load typogrify_tags %}
+{% load pagination_tags %}
+
+{% block pagetitle %}Luxagraf | Plants and Animals seen {% if region %}in {{region.name|title|smartypants|safe}}{%else%}by {{user}}{%endif%}{% if page != "1" %} -- Page {{page}}{%endif%}{% endblock %}
+{% block metadescription %}Plants and Animals seen {% if region %} in {{region.name|title|smartypants|safe}}{%else%}by {{user}}{%endif%} Page {{page}}{% endblock %}
+{%block bodyid%}id="birds"{%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="{% url 'sightings:list' %}" title="See all" itemprop="url"><span itemprop="title">Plants and Animals</span></a> &rarr;</li>
+ <li itemprop="title">the United States</li>{%else%}<li><a href="/jrnl/" title="See all Journal Entries" itemprop="url"><span>Plants and Animals</span></a> &rarr;</li>
+ <li>{{region.name|title|smartypants|safe}}</li>{%endif%}{%else%}<li>Plants and Animals</li>{%endif%}
+ </ul>
+ <main role="main" class="archive">{% autopaginate object_list 40 %}
+ <h1 class="hide">Plants and Animals seen {% if region %}in {%if region.name == 'United States'%}the United States{%else%}{{region.name|title|smartypants|safe}}{%endif%}{%else%} by {{user}}{%endif%}</h1> {% for object in object_list %}
+ <article class="{% cycle 'odd' 'even' %} {% cycle 'first' 'second' 'third' %}">
+ <div class="post--image">
+{% load get_image_by_size %}
+{% load get_image_width %}
+<figure>
+<a href="{{object.ap.get_absolute_url}}" title="{{object.ap}}">
+<img class="picfull" sizes="(max-width: 680px) 100vw, (min-width: 681) 680px" srcset="{% for size in object.image.sizes.all%}{% get_image_by_size object.image size.name %} {{size.width}}w{% if forloop.last%}"{%else%}, {%endif%}{%endfor%}
+ {% for size in object.image.sizes.all%}{%if forloop.first %} src="{% get_image_by_size object.image size.name %}"{%endif%}{%endfor%} alt="{{object.image.alt}} photographed by {% if object.image.photo_credit_source %}{{object.image.photo_credit_source}}{%else%}luxagraf{%endif%}" >
+</a>
+{% if object.image.photo_credit_source %}<figcaption>photo by <a href="{{object.image.photo_credit_url}}">{{object.image.photo_credit_source}}</a></figcaption>{%endif%}
+</figure>
+ </div>
+ <h3 class="post--title"><a href="{{object.ap.get_absolute_url}}" title="{%if object.title_keywords%}{{object.title_keywords}}{%else%}{{object.ap}}{%endif%}">{{object.ap|safe|smartypants|widont}}</a> (<span class="sci">{{object.ap.scientific_name}}</span>)</h3>
+ <time class="post--date" datetime="{{object.date|date:'c'}}">Seen: {{object.date|date:"F"}} <span>{{object.date|date:"j, Y"}}</span></time>
+ <p>
+ <span class="sighting location place post--location" itemscope itemtype="http://schema.org/Place">Loc:
+ {% if object.country.name == "United States" %}<span class="p-locality locality">{{object.location.name|smartypants|safe}}</span>, {{object.state.name}}, <span class="p-country-name">U.S.</span>{%else%}<span class="p-region">{{object.location.name|smartypants|safe}}</span>, {{object.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>
+ </p>
+ <div class="audio-figure">{% for recording in object.ap.recordings.all %}
+ <audio controls="controls">
+ <source src="/media/{{recording.audio}}" />
+ </audio>
+ <small>Audio recorded by {{recording.recorder}} on {{recording.pub_date|date:"F j, Y"}} in {{recording.location}}. <a href="{{recording.link}}">&copy; {{recording.copyright}}</a></small>
+ </div>{% endfor %}
+ </article> {% endfor %}
+ </main>
+ <nav class="pagination">
+ {% paginate %}
+ </nav>
+{% endblock %}
+
+
+
+{% block js %}<script src="/media/js/hyphenate.min.js" type="text/javascript"></script>{% endblock%}
diff --git a/design/templates/details/entry.html b/design/templates/details/entry.html
index d5038f2..b2a1058 100644
--- a/design/templates/details/entry.html
+++ b/design/templates/details/entry.html
@@ -53,6 +53,12 @@ class="{%if t == 1 or t == 3 or t == 5 %}double{%else%}single{%endif%}{%if t ==
<div id="article" class="e-content entry-content post--body post--body--{% with object.template_name as t %}{%if t == 0 or t == 2 %}single{%endif%}{%if t == 1 or t == 3 %}double{%endif%}{%endwith%}" itemprop="articleBody">
{{object.body_html|safe|smartypants|widont}}
</div>
+ <aside class="wildlife">
+ <h3>Fauna and Flora</h3>
+ <ul>{% for obj in wildlife %}
+ <li>{{obj}} </li>
+ {% endfor %}</ul>
+ </aside>
</article>
{% with object.get_next_published as next %}
{% with object.get_previous_published as prev %}
diff --git a/design/templates/details/sighting.html b/design/templates/details/sighting.html
new file mode 100644
index 0000000..a9bbef8
--- /dev/null
+++ b/design/templates/details/sighting.html
@@ -0,0 +1,56 @@
+{% extends 'base.html' %}
+{% load get_image_by_size %}
+{% block extrahead %}
+<style>
+#detail-map-canvas { height: 100%;}
+</style>
+{% endblock %}
+{% block bodyid %}class="birds"{%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>
+ <li><a href="{% url 'sightings:list' %}" title="See all" itemprop="url"><span itemprop="title">Plants and Animals</span></a></li>
+ </ul>
+
+<main>
+<article>
+<h1>{{object.common_name}}</h1>
+<h2 class="sci">{{object.scientific_name}}</h2>
+<h3 class="sci">Family {{object.apclass.scientific_name}} ({{object.apclass}})</h3>
+{% load get_image_by_size %}
+{% load get_image_width %}
+<div class="post--image">
+<figure>
+<img class="picfull" sizes="(max-width: 680px) 100vw, (min-width: 681) 680px" srcset="{% for size in sighting.image.sizes.all%}{% get_image_by_size sighting.image size.name %} {{size.width}}w{% if forloop.last%}"{%else%}, {%endif%}{%endfor%}
+ {% for size in sighting.image.sizes.all%}{%if forloop.first %} src="{% get_image_by_size sighting.image size.name %}"{%endif%}{%endfor%} alt="{{sighting.image.alt}} photographed by {% if sighting.image.photo_credit_source %}{{sighting.image.photo_credit_source}}{%else%}luxagraf{%endif%}" >
+{% if sighting.image.photo_credit_source %}<figcaption>photo by <a href="{{sighting.image.photo_credit_url}}">{{sighting.image.photo_credit_source}}</a></figcaption>{%endif%}
+</figure>
+</div>
+{%if recording %}
+<div class="audio-figure">
+<audio autoplay="autoplay" controls="controls">
+ <source src="/media/{{recording.audio}}" />
+</audio>
+<small>Audio recorded by {{recording.recorder}} on {{recording.pub_date|date:"F j, Y"}} in {{recording.location}}. <a href="{{recording.link}}">&copy; {{recording.copyright}}</a></small>
+</div>
+{%endif%}
+<p id="endnode">Seen at {%for sight in sighting %}{{sight.location}}, {{sight.location.comma_name}} in {{sight.date|date:"M Y"}} by {% for person in sight.seen_by.all %}<a href="{% url 'sightings:list_by_person' person %}">{% if person.username == "luxagraf"%}Scott{%else%}{{person.username|capfirst}}{%endif%}</a>{%if forloop.last %}. {%else%}{% if forloop.revcounter == 2 %}, and {%else%}, {%endif%}{%endif%}{%endfor%}{%endfor%} </p>
+{% if recording.audio %}
+{%endif%}
+</article>
+</main>
+{% endblock %}
+
+{% block js %}
+<script src="/media/js/leaflet-master/leaflet-mod.js"></script>
+<script src="/media/js/detail.js"></script>
+<script type="text/javascript">
+sightings = [];
+{% for s in sighting %}
+sightings.push(['{{s.latitude}}', '{{s.longitude}}']);
+{% endfor %}
+window.onload = function() {
+ createBirdMap({% for s in sighting %}{% if forloop.first %}{{s.latitude}},{{s.longitude}}{%endif%}{%endfor%}, 5, { type:'point', lat:'{{sighting.latitude}}', lon:'{{sighting.longitude}}'}, sightings); return false;
+}
+</script>
+{% endblock %}