diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/birds/admin.py | 52 | ||||
-rw-r--r-- | app/birds/models.py | 149 | ||||
-rw-r--r-- | app/jrnl/views.py | 10 | ||||
-rw-r--r-- | app/sightings/__init__.py | 0 | ||||
-rw-r--r-- | app/sightings/admin.py | 49 | ||||
-rw-r--r-- | app/sightings/autocomplete_light_registry.py | 24 | ||||
-rw-r--r-- | app/sightings/build.py | 21 | ||||
-rw-r--r-- | app/sightings/migrations/0001_initial.py | 72 | ||||
-rw-r--r-- | app/sightings/migrations/__init__.py | 0 | ||||
-rw-r--r-- | app/sightings/models.py | 175 | ||||
-rw-r--r-- | app/sightings/urls.py | 34 | ||||
-rw-r--r-- | app/sightings/views.py | 48 |
12 files changed, 621 insertions, 13 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 |