diff --git a/apps/map/forms.py b/apps/map/forms.py index 9af63f7..44b8351 100644 --- a/apps/map/forms.py +++ b/apps/map/forms.py @@ -1,13 +1,14 @@ -from django import forms from django.urls import reverse from crispy_forms.helper import FormHelper -from crispy_forms.layout import Submit +from crispy_forms.layout import Submit, Layout +from crispy_forms.bootstrap import Tab, TabHolder from leaflet.forms.widgets import LeafletWidget +from moderation.forms import BaseModeratedObjectForm from .models import CaseStudy -class BaseCaseStudyForm(forms.ModelForm): +class BaseCaseStudyForm(BaseModeratedObjectForm): """Base form class for the CaseStudy model.""" def __init__(self, *args, **kwargs): super(BaseCaseStudyForm, self).__init__(*args, **kwargs) @@ -52,6 +53,11 @@ class ShortCaseStudyForm(BaseCaseStudyForm): 'synopsis', 'full_description', 'image', + 'image_caption', + 'image_credit', + 'video', + 'media_coverage_mainstream', + 'media_coverage_independent', 'community_voices' ] @@ -61,6 +67,47 @@ class LongCaseStudyForm(BaseCaseStudyForm): def __init__(self, *args, **kwargs): super(LongCaseStudyForm, self).__init__(*args, **kwargs) self.helper.form_action = reverse('long-form') + self.helper.layout = Layout( + TabHolder( + Tab("First Page", + 'entry_name', + 'location', + 'sector_of_economy', + 'positive_or_negative', + 'country', + 'area_of_land', + 'land_ownership', + 'land_ownership_details', + 'location_context', + 'type_of_ecosystem', + 'describe_ecosystem', + 'affects_indigenous', + 'affects_indigenous_detail', + 'project_status', + 'synopsis', + 'full_description', + 'image', + 'image_caption', + 'image_credit', + 'video', + 'media_coverage_mainstream', + 'media_coverage_independent', + 'community_voices'), + Tab( + "Second Page", + 'generation_technology', + 'biomass_detail', + 'generation_technology_other', + 'total_generation_capacity', + 'total_investment', + 'technical_or_economic_details', + 'power_technology', + 'power_technology_other', + 'energy_storage_capacity', + ))) + + + class Meta(BaseCaseStudyForm.Meta): - fields = '__all__' + fields = '__all__' \ No newline at end of file diff --git a/apps/map/migrations/0010_auto_20171011_1606.py b/apps/map/migrations/0010_auto_20171011_1606.py new file mode 100644 index 0000000..4620864 --- /dev/null +++ b/apps/map/migrations/0010_auto_20171011_1606.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-10-11 16:06 +from __future__ import unicode_literals + +import apps.map.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0009_auto_20171007_1544'), + ] + + operations = [ + migrations.AddField( + model_name='casestudy', + name='completion_year', + field=models.IntegerField(blank=True, choices=[(1977, 1977), (1978, 1978), (1979, 1979), (1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020), (2021, 2021), (2022, 2022), (2023, 2023), (2024, 2024), (2025, 2025), (2026, 2026), (2027, 2027), (2028, 2028), (2029, 2029), (2030, 2030), (2031, 2031), (2032, 2032), (2033, 2033), (2034, 2034), (2035, 2035), (2036, 2036), (2037, 2037), (2038, 2038), (2039, 2039), (2040, 2040), (2041, 2041), (2042, 2042), (2043, 2043), (2044, 2044), (2045, 2045), (2046, 2046), (2047, 2047), (2048, 2048), (2049, 2049), (2050, 2050), (2051, 2051), (2052, 2052), (2053, 2053), (2054, 2054), (2055, 2055), (2056, 2056), (2057, 2057)], default=None, help_text="Select the year the project was completed. If the project hasn't finished, select the projected completion year.", null=True, verbose_name='Completion year'), + ), + migrations.AddField( + model_name='casestudy', + name='direct_comms', + field=models.TextField(default=None, help_text='Add any reports of direct communication between community members and representatives of developers/companies/investors.', max_length=500, null=True, verbose_name='Reports of direct communications'), + ), + migrations.AddField( + model_name='casestudy', + name='energy_customers', + field=models.CharField(blank=True, default=None, help_text="List any wholesale energy customers that take energy from the development. E.g. 'national grids' or private energy suppliers.", max_length=120, null=True, verbose_name='Energy consumers'), + ), + migrations.AddField( + model_name='casestudy', + name='financial_institutions', + field=models.CharField(blank=True, default=None, help_text='List banks and other financial institutions that have or are considering extending loans or guarantees to the project. Separate with a comma.', max_length=120, null=True, verbose_name='Financial institutions'), + ), + migrations.AddField( + model_name='casestudy', + name='image_caption', + field=models.CharField(default=None, max_length=500, null=True, verbose_name='Image caption'), + ), + migrations.AddField( + model_name='casestudy', + name='image_credit', + field=models.CharField(default=None, max_length=200, null=True, verbose_name='Image credit(s)'), + ), + migrations.AddField( + model_name='casestudy', + name='media_coverage_independent', + field=models.TextField(default=None, help_text='Provide any links to grassroots/independent media coverage.', max_length=500, null=True, verbose_name='Independent grassroots reports'), + ), + migrations.AddField( + model_name='casestudy', + name='media_coverage_mainstream', + field=models.TextField(default=None, help_text='Provide any links to mainstream media coverage.', max_length=500, null=True, verbose_name='Links to media reports'), + ), + migrations.AddField( + model_name='casestudy', + name='project_owners', + field=models.CharField(blank=True, default=None, help_text='List companies or organisations that own the project and/or facilities. Separate with a comma.', max_length=120, null=True, verbose_name='Project and facility owners'), + ), + migrations.AddField( + model_name='casestudy', + name='shareholders', + field=models.CharField(blank=True, default=None, help_text="List shareholders of the project owners you've just listed. Separate with a comma.", max_length=120, null=True, verbose_name='Shareholders of the project owners'), + ), + migrations.AddField( + model_name='casestudy', + name='social_media_links', + field=models.TextField(blank=True, default=None, help_text='Add any links to social media accounts directly relating to the project.', max_length=500, null=True, verbose_name='Social media links'), + ), + migrations.AddField( + model_name='casestudy', + name='start_year', + field=models.IntegerField(blank=True, choices=[(1977, 1977), (1978, 1978), (1979, 1979), (1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020), (2021, 2021), (2022, 2022), (2023, 2023), (2024, 2024), (2025, 2025), (2026, 2026), (2027, 2027), (2028, 2028), (2029, 2029), (2030, 2030), (2031, 2031), (2032, 2032), (2033, 2033), (2034, 2034), (2035, 2035), (2036, 2036), (2037, 2037), (2038, 2038), (2039, 2039), (2040, 2040), (2041, 2041), (2042, 2042), (2043, 2043), (2044, 2044), (2045, 2045), (2046, 2046), (2047, 2047), (2048, 2048), (2049, 2049), (2050, 2050), (2051, 2051), (2052, 2052), (2053, 2053), (2054, 2054), (2055, 2055), (2056, 2056), (2057, 2057)], default=None, help_text="Select the year the project was started. If the project hasn't begun, select the projected start year.", null=True, verbose_name='Start year'), + ), + migrations.AddField( + model_name='casestudy', + name='video_caption', + field=models.CharField(default=None, max_length=500, null=True, verbose_name='Video caption'), + ), + migrations.AddField( + model_name='casestudy', + name='video_credit', + field=models.CharField(default=None, max_length=500, null=True, verbose_name='Video credit(s)'), + ), + migrations.AlterField( + model_name='casestudy', + name='affects_indigenous', + field=models.BooleanField(help_text='Does the project affect indigenous communities?', verbose_name='Affects indigenous people?'), + ), + migrations.AlterField( + model_name='casestudy', + name='community_voices', + field=models.TextField(help_text='Add any direct quotes from members of the community that relate to this project', verbose_name='Community Voices'), + ), + migrations.AlterField( + model_name='casestudy', + name='date_created', + field=models.DateTimeField(auto_now_add=True), + ), + migrations.AlterField( + model_name='casestudy', + name='land_ownership_details', + field=models.CharField(blank=True, help_text="Please specify details about land ownership if you chose 'other'", max_length=256, null=True, verbose_name='Land ownership details'), + ), + migrations.AlterField( + model_name='casestudy', + name='video', + field=models.URLField(help_text='Copy the URL to a related YouTube™ video that relates to the case study.', max_length=43, validators=[apps.map.validators.YoutubeURLValidator()], verbose_name='YouTube Video'), + ), + ] diff --git a/apps/map/migrations/0011_casestudy_generation_technology.py b/apps/map/migrations/0011_casestudy_generation_technology.py new file mode 100644 index 0000000..9566268 --- /dev/null +++ b/apps/map/migrations/0011_casestudy_generation_technology.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-10-12 15:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0010_auto_20171011_1606'), + ] + + operations = [ + migrations.AddField( + model_name='casestudy', + name='generation_technology', + field=models.CharField(blank=True, choices=[('SSWE', 'Small-scale wind energy (less than 500kW)'), ('LSWE', 'Large-scale wind energy (more than 500kW)'), ('SSPV', 'Small-scale photovoltaic electricity (less than 500kW)'), ('LSPV', 'Large-scale photovoltaic electricity (more than 500kW)'), ('STHE', 'Solar thermal electricity (e.g using parabolic reflectors)'), ('SHYD', 'Small hydroelectric (less than 1MW)'), ('MHYD', 'Medium hydroelectric (between 1-20MW)'), ('LHYD', 'Large hydroelectric (more than 20MW - often not considered renewable)'), ('GEOT', 'Geothermal electricity'), ('BIOG', 'Biogas turbine'), ('OTHB', 'Other biomass (including liquid/solid biofuel)')], default=None, help_text='Select the type of renewable energy generation that most applies to this case study.', max_length=4, null=True, verbose_name='Generation technology'), + ), + ] diff --git a/apps/map/migrations/0012_auto_20171012_1610.py b/apps/map/migrations/0012_auto_20171012_1610.py new file mode 100644 index 0000000..425c272 --- /dev/null +++ b/apps/map/migrations/0012_auto_20171012_1610.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-10-12 16:10 +from __future__ import unicode_literals + +import django.contrib.gis.db.models.fields +from django.db import migrations, models +import django_countries.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0011_casestudy_generation_technology'), + ] + + operations = [ + migrations.AddField( + model_name='casestudy', + name='biomass_detail', + field=models.CharField(blank=True, default=None, help_text='If you selected biogas or biomass, please describe the feedstock (where the fuel came from e.g. corn, algae, anaerobic digestion, commercial waste etc)', max_length=200, null=True, verbose_name='Generation technology detail'), + ), + migrations.AddField( + model_name='casestudy', + name='generation_technology_other', + field=models.CharField(blank=True, default=None, help_text='If you selected other, please specify the generation technology (e.g. tidal, wave etc)', max_length=200, null=True, verbose_name='Other generation type'), + ), + migrations.AddField( + model_name='casestudy', + name='total_generation_capacity', + field=models.IntegerField(blank=True, default=None, help_text='Please enter the total generation capacity of the project in kW', null=True, verbose_name='Total generation capacity (in kW)'), + ), + migrations.AlterField( + model_name='casestudy', + name='country', + field=django_countries.fields.CountryField(help_text='Select the country of the project', max_length=2, verbose_name='Country field'), + ), + migrations.AlterField( + model_name='casestudy', + name='generation_technology', + field=models.CharField(blank=True, choices=[('SSWE', 'Small-scale wind energy (less than 500kW)'), ('LSWE', 'Large-scale wind energy (more than 500kW)'), ('SSPV', 'Small-scale photovoltaic electricity (less than 500kW)'), ('LSPV', 'Large-scale photovoltaic electricity (more than 500kW)'), ('STHE', 'Solar thermal electricity (e.g using parabolic reflectors)'), ('SHYD', 'Small hydroelectric (less than 1MW)'), ('MHYD', 'Medium hydroelectric (between 1-20MW)'), ('LHYD', 'Large hydroelectric (more than 20MW - often not considered renewable)'), ('GEOT', 'Geothermal electricity'), ('BIOG', 'Biogas turbine'), ('OTHB', 'Other biomass (including liquid/solid biofuel)'), ('OTHR', 'Other (tidal, wave etc)')], default=None, help_text='Select the type of renewable energy generation that most applies to this case study.', max_length=4, null=True, verbose_name='Generation technology'), + ), + migrations.AlterField( + model_name='casestudy', + name='location', + field=django.contrib.gis.db.models.fields.PointField(help_text='Place a marker using the tools on the left of the map. Zoom in as far as you can so the placement is accurate (important)', srid=4326, verbose_name='Project location'), + ), + ] diff --git a/apps/map/migrations/0013_auto_20171012_1640.py b/apps/map/migrations/0013_auto_20171012_1640.py new file mode 100644 index 0000000..c73f169 --- /dev/null +++ b/apps/map/migrations/0013_auto_20171012_1640.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-10-12 16:40 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0012_auto_20171012_1610'), + ] + + operations = [ + migrations.AddField( + model_name='casestudy', + name='technical_or_economic_details', + field=models.CharField(blank=True, default=None, help_text='Specify any additional technical or economic details relating to the project.', max_length=500, null=True, verbose_name='Additional technical or economic details'), + ), + migrations.AddField( + model_name='casestudy', + name='total_investment', + field=models.IntegerField(blank=True, default=None, help_text='The approximate total investment for the project in USD.', null=True, verbose_name='Total investment (in USD)'), + ), + migrations.AlterField( + model_name='casestudy', + name='generation_technology', + field=models.CharField(blank=True, choices=[('Wind energy', (('SSWE', 'Small-scale (less than 500kW)'), ('LSWE', 'Large-scale (more than 500kW)'))), ('Photovoltaic electricity', (('SSPV', 'Small-scale (less than 500kW)'), ('LSPV', 'Large-scale (more than 500kW)'))), ('Hydroelectric', (('SHYD', 'Small-scale (less than 1MW)'), ('MHYD', 'Medium-scale (between 1-20MW)'), ('LHYD', 'Large-scale (more than 20MW - often not considered renewable)'))), ('STHE', 'Solar thermal electricity (e.g using parabolic reflectors)'), ('GEOT', 'Geothermal electricity'), ('BIOG', 'Biogas turbine'), ('OTHB', 'Other biomass (including liquid/solid biofuel)'), ('OTHR', 'Other (tidal, wave etc)')], default=None, help_text='Select the type of renewable energy generation that most applies to this case study.', max_length=4, null=True, verbose_name='Generation technology'), + ), + migrations.AlterField( + model_name='casestudy', + name='total_generation_capacity', + field=models.PositiveIntegerField(blank=True, default=None, help_text='Please enter the total generation capacity of the project in kW', null=True, verbose_name='Total generation capacity (in kW)'), + ), + ] diff --git a/apps/map/migrations/0014_auto_20171025_2035.py b/apps/map/migrations/0014_auto_20171025_2035.py new file mode 100644 index 0000000..8474aed --- /dev/null +++ b/apps/map/migrations/0014_auto_20171025_2035.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-10-25 20:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0013_auto_20171012_1640'), + ] + + operations = [ + migrations.AddField( + model_name='casestudy', + name='power_technology', + field=models.CharField(blank=True, choices=[('PT', 'Power transmission (grid lines, substations etc)'), ('ES', 'Energy storage (pumped storage, compressed air, battery systems etc'), ('OT', 'Others')], default=None, help_text='Select the related energy technology.', max_length=2, null=True, verbose_name='Power technology'), + ), + migrations.AddField( + model_name='casestudy', + name='power_technology_other', + field=models.CharField(blank=True, default=None, help_text="If you answered 'others', please specify the power technologies.", max_length=128, null=True, verbose_name='Other power technology'), + ), + ] diff --git a/apps/map/migrations/0015_auto_20171030_1550.py b/apps/map/migrations/0015_auto_20171030_1550.py new file mode 100644 index 0000000..d5cf2aa --- /dev/null +++ b/apps/map/migrations/0015_auto_20171030_1550.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-10-30 15:50 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0014_auto_20171025_2035'), + ] + + operations = [ + migrations.AddField( + model_name='casestudy', + name='energy_storage_capacity', + field=models.IntegerField(blank=True, default=None, help_text='Enter the total capacity of the energy storage system.', null=True, verbose_name='Energy storage capacity'), + ), + migrations.AlterField( + model_name='casestudy', + name='affects_indigenous_detail', + field=models.CharField(blank=True, default=None, help_text='What group of indigenous people does the community belong to?', max_length=256, null=True, verbose_name='Affects Indigenous - Details'), + ), + ] diff --git a/apps/map/migrations/0016_auto_20171031_1442.py b/apps/map/migrations/0016_auto_20171031_1442.py new file mode 100644 index 0000000..2b7a9ba --- /dev/null +++ b/apps/map/migrations/0016_auto_20171031_1442.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-10-31 14:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0015_auto_20171030_1550'), + ] + + operations = [ + migrations.AddField( + model_name='casestudy', + name='definition_of_affected_territories', + field=models.CharField(blank=True, default=None, help_text='In your own words, define the territories that the project will affect.', max_length=512, null=True, verbose_name='Definition of affected territories'), + ), + migrations.AddField( + model_name='casestudy', + name='official_project_documents', + field=models.FileField(blank=True, default=None, help_text='Attach any legal or official documents that relate to the project.', null=True, upload_to='', verbose_name='Official project documents'), + ), + migrations.AddField( + model_name='casestudy', + name='other_documents', + field=models.FileField(blank=True, default=None, help_text='Attach any other documents that relate to the project.', null=True, upload_to='', verbose_name='Other documents'), + ), + migrations.AddField( + model_name='casestudy', + name='shown_on_other_platforms', + field=models.NullBooleanField(default=None, help_text='Tick the box if you would like us to show this case study on other social media platforms', verbose_name='Show on other platforms?'), + ), + migrations.AddField( + model_name='casestudy', + name='shown_on_other_platforms_detail', + field=models.CharField(blank=True, help_text='List the social media platforms that you would like us to specifically publish the case study on', max_length=128, null=True, verbose_name='Show on other platforms - Detail'), + ), + ] diff --git a/apps/map/models.py b/apps/map/models.py index 75ec87c..9c294c2 100644 --- a/apps/map/models.py +++ b/apps/map/models.py @@ -1,9 +1,12 @@ +import datetime +from urllib import parse from django.contrib.gis.db import models from django.contrib.auth.models import User from django_extensions.db.fields import AutoSlugField from django_countries.fields import CountryField from django.utils.translation import ugettext as _ from django.template.defaultfilters import slugify +from . import validators class CaseStudy(models.Model): @@ -58,6 +61,38 @@ class CaseStudy(models.Model): ('PROJCD', _('Projected Project')), ) + GENERATION_TECHNOLOGY_CHOICES = ( + (_('Wind energy'), ( + ('SSWE', _('Small-scale (less than 500kW)')), + ('LSWE', _('Large-scale (more than 500kW)')) + )), + (_('Photovoltaic electricity'), ( + ('SSPV', _('Small-scale (less than 500kW)')), + ('LSPV', _('Large-scale (more than 500kW)')) + )), + (_('Hydroelectric'), ( + ('SHYD', _('Small-scale (less than 1MW)')), + ('MHYD', _('Medium-scale (between 1-20MW)')), + ('LHYD', _('Large-scale (more than 20MW - often not considered renewable)')), + )), + ('STHE', _('Solar thermal electricity (e.g using parabolic reflectors)')), + ('GEOT', _('Geothermal electricity')), + ('BIOG', _('Biogas turbine')), + ('OTHB', _('Other biomass (including liquid/solid biofuel)')), + ('OTHR', _('Other (tidal, wave etc)')) + ) + + POWER_TECHNOLOGY_CHOICES = ( + ('PT', _('Power transmission (grid lines, substations etc)')), + ('ES', _('Energy storage (pumped storage, compressed air, battery systems etc')), + ('OT', _('Others')) + ) + + # Dynamically generate a list of choices 40 years prior and after the current year. + YEAR_CHOICES = [(r, r) for r in + range((datetime.datetime.now().year - 40), + (datetime.datetime.now().year + 41))] + ## # Meta Fields ## @@ -72,11 +107,16 @@ class CaseStudy(models.Model): ) # Date and time of submission - date_created = models.DateTimeField(auto_now=True, null=False) + date_created = models.DateTimeField(auto_now_add=True, null=False) # Slug derived from entry_name, used in urls for SEO + # TODO: Change this so it's not dynamic. Slugs should never change. slug = AutoSlugField(populate_from=['entry_name']) + ## + # First Screen + ## + # 1.1 entry_name = models.CharField( verbose_name=_("Entry Name"), @@ -86,7 +126,11 @@ class CaseStudy(models.Model): ) # N/A - Not explicitly listed in spec - location = models.PointField() + location = models.PointField( + verbose_name=_("Project location"), + help_text=_("Place a marker using the tools on the left of the map. Zoom in as far as you can so the placement \ + is accurate (important)") + ) # 1.2 sector_of_economy = models.CharField( @@ -106,7 +150,10 @@ class CaseStudy(models.Model): ) # 1.4 - country = CountryField() + country = CountryField( + verbose_name=_("Country field"), + help_text=_("Select the country of the project") + ) # 1.5.1 area_of_land = models.IntegerField( @@ -125,9 +172,10 @@ class CaseStudy(models.Model): # 1.5.3 land_ownership_details = models.CharField( verbose_name=_("Land ownership details"), - help_text=_("Add any details and other remarks about the land\ - ownership"), - max_length=256 + help_text=_("Please specify details about land ownership if you chose 'other'"), + max_length=256, + null=True, + blank=True, ) # 1.5.4 @@ -157,7 +205,7 @@ class CaseStudy(models.Model): # 1.5.6 affects_indigenous = models.BooleanField( verbose_name=_("Affects indigenous people?"), - help_text=_("Does the project affect indigenous people?") + help_text=_("Does the project affect indigenous communities?") ) # 1.5.6.1 @@ -165,7 +213,10 @@ class CaseStudy(models.Model): verbose_name=_("Affects Indigenous - Details"), help_text=_("What group of indigenous people does the community belong\ to?"), - max_length=256 + max_length=256, + default=None, + null=True, + blank=True ) # 1.6 @@ -176,6 +227,28 @@ class CaseStudy(models.Model): choices=PROJECT_STATUS_CHOICES ) + # 1.7 + start_year = models.IntegerField( + verbose_name=_("Start year"), + help_text=_("Select the year the project was started. \ + If the project hasn't begun, select the projected start year."), + choices=YEAR_CHOICES, + default=None, + null=True, + blank=True + ) + + # 1.8 + completion_year = models.IntegerField( + verbose_name=_("Completion year"), + help_text=_("Select the year the project was completed. \ + If the project hasn't finished, select the projected completion year."), + choices=YEAR_CHOICES, + default=None, + null=True, + blank=True + ) + # 1.9 synopsis = models.TextField( verbose_name=_("Synopsis"), @@ -193,24 +266,287 @@ class CaseStudy(models.Model): here.") ) - # 1.15 + # 1.11 + project_owners = models.CharField( + verbose_name=_("Project and facility owners"), + help_text=_("List companies or organisations that own the project and/or facilities. Separate with a comma."), + max_length=120, + default=None, + null=True, + blank=True + ) + + # 1.12 + shareholders = models.CharField( + verbose_name=_("Shareholders of the project owners"), + help_text=_("List shareholders of the project owners you've just listed. Separate with a comma."), + max_length=120, + default=None, + null=True, + blank=True + ) + + # 1.13 + financial_institutions = models.CharField( + verbose_name=_("Financial institutions"), + help_text=_("List banks and other financial institutions that have or are considering extending loans \ + or guarantees to the project. Separate with a comma."), + max_length=120, + default=None, + null=True, + blank=True + ) + + # 1.14 + energy_customers = models.CharField( + verbose_name=_("Energy consumers"), + help_text=_("List any wholesale energy customers that take energy from the development. E.g. 'national \ + grids' or private energy suppliers."), + max_length=120, + default=None, + null=True, + blank=True + ) + + # 1.15.1 image = models.ImageField( verbose_name=_("Image") ) - # 1.16 - video = models.URLField( - verbose_name=_("Video"), - max_length=43 + # 1.15.2 + image_caption = models.CharField( + verbose_name=_("Image caption"), + max_length=500, + default=None, + null=True, ) - # 1.18 + # 1.15.3 + image_credit = models.CharField( + verbose_name=_("Image credit(s)"), + max_length=200, + default=None, + null=True, + ) + + # 1.16.1 + video = models.URLField( + verbose_name=_("YouTube Video"), + help_text=_("Copy the URL to a related YouTube™ video that relates to the case study."), + max_length=43, + validators=[validators.YoutubeURLValidator()] + ) + + # 1.16.2 + video_caption = models.CharField( + verbose_name=_("Video caption"), + max_length=500, + default=None, + null=True, + ) + + # 1.16.3 + video_credit = models.CharField( + verbose_name=_("Video credit(s)"), + max_length=500, + default=None, + null=True, + ) + # 1.17.1 + media_coverage_mainstream = models.TextField( + verbose_name=_("Links to media reports"), + help_text=_("Provide any links to mainstream media coverage."), + max_length=500, + default=None, + null=True, + ) + + # 1.17.2 + media_coverage_independent = models.TextField( + verbose_name=_("Independent grassroots reports"), + help_text=_("Provide any links to grassroots/independent media coverage."), + max_length=500, + default=None, + null=True, + ) + + # 1.18.1 community_voices = models.TextField( verbose_name=_("Community Voices"), - help_text=_("Add any direct quotes from members of the community that\ + help_text=_("Add any direct quotes from members of the community that \ relate to this project") ) + # 1.18.2 + direct_comms = models.TextField( + verbose_name=_("Reports of direct communications"), + help_text=_("Add any reports of direct communication between community members and \ + representatives of developers/companies/investors."), + max_length=500, + default=None, + null=True, + ) + + # 1.18.3 + social_media_links = models.TextField( + verbose_name=_("Social media links"), + help_text=_("Add any links to social media accounts directly relating to the project."), + max_length=500, + default=None, + null=True, + blank=True + ) + + ## + # Second Screen + ## + + # 2.1.1 + generation_technology = models.CharField( + verbose_name=_("Generation technology"), + help_text=_("Select the type of renewable energy generation that most applies to this case study."), + max_length=4, + choices=GENERATION_TECHNOLOGY_CHOICES, + default=None, + null=True, + blank=True + ) + + # 2.1.1.12 + # Should be filled in if 2.1.1 was answered as biogas or biomass. + biomass_detail = models.CharField( + verbose_name=_("Generation technology detail"), + help_text=_("If you selected biogas or biomass, please describe the feedstock (where the fuel came from e.g. \ + corn, algae, anaerobic digestion, commercial waste etc)"), + max_length=200, + default=None, + null=True, + blank=True + ) + + # 2.1.1.14 + generation_technology_other = models.CharField( + verbose_name=_("Other generation type"), + help_text=_("If you selected other, please specify the generation technology (e.g. tidal, wave etc)"), + max_length=200, + default=None, + null=True, + blank=True + ) + + # 2.1.2 + total_generation_capacity = models.PositiveIntegerField( + verbose_name=_("Total generation capacity (in kW)"), + help_text=_("Please enter the total generation capacity of the project in kW"), + default=None, + null=True, + blank=True, + ) + + # 2.1.3 - autocomplete_light queryset + + # 2.1.4 + total_investment = models.IntegerField( + verbose_name=_("Total investment (in USD)"), + help_text=_("The approximate total investment for the project in USD."), + default=None, + null=True, + blank=True + ) + + # 2.1.5 + technical_or_economic_details = models.CharField( + verbose_name=_("Additional technical or economic details"), + help_text=_("Specify any additional technical or economic details relating to the project."), + max_length=500, + default=None, + null=True, + blank=True + ) + + # 2.2 - Only if 1.2.2 selected + # 2.2.1 + power_technology = models.CharField( + verbose_name=_("Power technology"), + help_text=_("Select the related energy technology."), + max_length=2, + choices=POWER_TECHNOLOGY_CHOICES, + default=None, + null=True, + blank=True + ) + + power_technology_other = models.CharField( + verbose_name=_("Other power technology"), + help_text=_("If you answered 'others', please specify the power technologies."), + max_length=128, + default=None, + null=True, + blank=True + ) + + energy_storage_capacity = models.IntegerField( + verbose_name=_("Energy storage capacity"), + help_text=_("Enter the total capacity of the energy storage system."), + default=None, + null=True, + blank=True + ) + + ## + # Third Screen + ## + + ## + # Fourth Screen + ## + + # 4.1 + official_project_documents = models.FileField( + verbose_name=_("Official project documents"), + help_text=_("Attach any legal or official documents that relate to the project."), + default=None, + null=True, + blank=True + ) + + # 4.2 + other_documents = models.FileField( + verbose_name=_("Other documents"), + help_text=_("Attach any other documents that relate to the project."), + default=None, + null=True, + blank=True + ) + + # 4.3 + definition_of_affected_territories = models.CharField( + verbose_name=_("Definition of affected territories"), + help_text=_("In your own words, define the territories that the project will affect."), + max_length=512, + default=None, + null=True, + blank=True + ) + + # 4.4 + shown_on_other_platforms = models.NullBooleanField( + verbose_name=_("Show on other platforms?"), + help_text=_("Tick the box if you would like us to show this case study on other social media platforms"), + default=None, + null=True, + blank=True + ) + + # 4.4.1 + shown_on_other_platforms_detail = models.CharField( + verbose_name=_("Show on other platforms - Detail"), + help_text=_("List the social media platforms that you would like us to specifically publish the case study on"), + max_length=128, + null=True, + blank=True + ) + def __str__(self): """The String representation of the case study. (Entry name with country name.)""" return "%s in %s" % (self.entry_name, self.country.name) @@ -227,3 +563,7 @@ class CaseStudy(models.Model): self.slug = slugify(self.entry_name) # Continue normal save method by calling original save method. super(CaseStudy, self).save(*args, **kwargs) + + def get_video_id(self): + """Gets the 11 character YouTube video ID from the video field.""" + return parse.parse_qs(parse.urlparse(self.video).query)["v"][0] diff --git a/apps/map/moderator.py b/apps/map/moderator.py index da91805..8e3005f 100644 --- a/apps/map/moderator.py +++ b/apps/map/moderator.py @@ -1,4 +1,10 @@ from moderation import moderation +from moderation.moderator import GenericModerator from apps.map.models import CaseStudy -moderation.register(CaseStudy) # Uses default moderation settings + +class CaseStudyModerator(GenericModerator): + notify_user = True + auto_approve_for_superusers = True + +moderation.register(CaseStudy, CaseStudyModerator) \ No newline at end of file diff --git a/apps/map/templates/map/detail.html b/apps/map/templates/map/detail.html index 7adc4d7..0827f9c 100644 --- a/apps/map/templates/map/detail.html +++ b/apps/map/templates/map/detail.html @@ -17,7 +17,7 @@
- <- {% trans "Back to Map" %} + {% trans "Back to Map" %}

{{case_study.entry_name}}

{{case_study.synopsis}}

@@ -33,13 +33,13 @@ {{case_study.country.name}}
Created {{case_study.date_created|naturaltime}} by {{case_study.author.}} -
+
Get Involved
- +
diff --git a/apps/map/templates/map/form.html b/apps/map/templates/map/form.html index d43f5a0..74268df 100644 --- a/apps/map/templates/map/form.html +++ b/apps/map/templates/map/form.html @@ -31,4 +31,95 @@ // See GeometryField source (static/leaflet/leaflet.forms.js) to override more stuff... }); + + + {% endblock %} diff --git a/apps/map/templates/map/index.html b/apps/map/templates/map/index.html index 34d8339..5d099ba 100644 --- a/apps/map/templates/map/index.html +++ b/apps/map/templates/map/index.html @@ -100,13 +100,13 @@ }).addTo(map); }); - // Add an on-click listener for map click events. Show popup with button to submit a casestudy - map.on('click', function(e) { - var popup = L.popup() - .setLatLng(e.latlng) - .setContent("{% trans "Submit a Case Study" %}") - .openOn(map); - }); + + + + + + + } {% endblock %} diff --git a/apps/map/validators.py b/apps/map/validators.py new file mode 100644 index 0000000..4d3ab80 --- /dev/null +++ b/apps/map/validators.py @@ -0,0 +1,5 @@ +from django.core.validators import RegexValidator + + +class YoutubeURLValidator(RegexValidator): + regex = r'https?:\/\/(((www.)?youtube.com\/((watch\?v=)|(watch\/)))|(youtu.be\/))([A-z0-9]{1,11}).+' diff --git a/apps/profiles/forms.py b/apps/profiles/forms.py new file mode 100644 index 0000000..acf04b8 --- /dev/null +++ b/apps/profiles/forms.py @@ -0,0 +1,7 @@ +from django.forms import ModelForm +from django.contrib.auth import get_user_model + + +class UpdateProfile(ModelForm): + class Meta: + model = get_user_model() \ No newline at end of file diff --git a/apps/profiles/templates/profiles/profile.html b/apps/profiles/templates/profiles/profile.html index 6ba97c8..2d890a7 100644 --- a/apps/profiles/templates/profiles/profile.html +++ b/apps/profiles/templates/profiles/profile.html @@ -1,2 +1,6 @@ {% extends "base_page.html" %} -Profile page \ No newline at end of file +Profile page +
{% csrf_token %} + {{ form.as_p }} + +
\ No newline at end of file diff --git a/apps/profiles/urls.py b/apps/profiles/urls.py index e006ca7..2ffa942 100644 --- a/apps/profiles/urls.py +++ b/apps/profiles/urls.py @@ -3,5 +3,19 @@ from django.conf.urls import url from . import views urlpatterns = [ - url(r'^$', views.Profile.as_view(), name='profile'), -] + url( + regex=r'^~redirect/$', + view=views.UserRedirectView.as_view(), + name='redirect' + ), + url( + regex=r'^$', + view=views.UserDetailView.as_view(), + name='detail' + ), + url( + regex=r'^~update/$', + view=views.UserUpdateView.as_view(), + name='update' + ), +] \ No newline at end of file diff --git a/apps/profiles/views.py b/apps/profiles/views.py index d67ec90..cf061f7 100644 --- a/apps/profiles/views.py +++ b/apps/profiles/views.py @@ -1,5 +1,46 @@ -from django.views.generic.base import TemplateView +from django.core.urlresolvers import reverse +from django.views.generic import DetailView, ListView, RedirectView, UpdateView + +from django.contrib.auth.mixins import LoginRequiredMixin + +from django.contrib.auth import get_user_model -class Profile(TemplateView): - template_name = "profiles/profile.html" +class UserDetailView(LoginRequiredMixin, DetailView): + model = get_user_model() + # These next two lines tell the view to index lookups by username + slug_field = 'username' + slug_url_kwarg = 'username' + + def get_object(self): + return get_user_model().objects.get(username=self.request.user.username) + + +class UserRedirectView(LoginRequiredMixin, RedirectView): + permanent = False + + def get_redirect_url(self): + return reverse('profile:detail') + + +class UserUpdateView(LoginRequiredMixin, UpdateView): + + fields = ['first_name', 'last_name'] + + # we already imported User in the view code above, remember? + model = get_user_model() + + # send the user back to their own page after a successful update + def get_success_url(self): + return reverse('profile:detail') + + def get_object(self): + # Only get the User record for the user making the request + return get_user_model().objects.get(username=self.request.user.username) + + +class UserListView(LoginRequiredMixin, ListView): + model = get_user_model() + # These next two lines tell the view to index lookups by username + slug_field = 'username' + slug_url_kwarg = 'username' diff --git a/docker-compose.yml b/docker-compose.yml index b7a3117..0d198e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,7 @@ services: volumes: - ./apps:/app/apps - ./ojusomap:/app/ojusomap + - ./support:/app/support - /containers/map/static:/app/static - /containers/map/gunicorn.sock:/app/gunicorn.sock env_file: diff --git a/local.yml b/local.yml index b4eceb5..1354fa1 100644 --- a/local.yml +++ b/local.yml @@ -1,4 +1,4 @@ -version: "3" +version: '3' services: map: build: . diff --git a/ojusomap/settings.py b/ojusomap/settings.py index 566484d..0b7072c 100644 --- a/ojusomap/settings.py +++ b/ojusomap/settings.py @@ -30,7 +30,10 @@ SECRET_KEY = os.getenv( # SECURITY WARNING: don't run with debug turned on in production! DEBUG = bool(int(os.getenv('DEBUG', False))) -ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost').split() +if DEBUG: + ALLOWED_HOSTS = ['*'] +else: + ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost').split() # Application definition @@ -51,6 +54,7 @@ INSTALLED_APPS = [ 'whitenoise.runserver_nostatic', 'django.contrib.staticfiles', 'django.contrib.gis', + 'avatar', 'bootstrap3', 'cas_server', 'compressor', @@ -62,6 +66,7 @@ INSTALLED_APPS = [ 'raven.contrib.django.raven_compat', 'rest_framework', 'rest_framework_gis', + 'storages' ] MIDDLEWARE = [ @@ -174,6 +179,16 @@ LOCALE_PATHS = [ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ +DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' + +AWS_S3_ENDPOINT_URL = "https://ojuso-media.nyc3.digitaloceanspaces.com" +AWS_STORAGE_BUCKET_NAME = "ojuso-media" +AWS_LOCATION = "" +AWS_S3_REGION_NAME = "nyc3" +AWS_S3_CUSTOM_DOMAIN = "ojuso-media.nyc3.digitaloceanspaces.com/ojuso-media" +AWS_ACCESS_KEY_ID = "RUZTFVKPCNJIWCVUVJFW" +AWS_SECRET_ACCESS_KEY = "pTZAnmtvGzFBdI/jwtwRoFW5eK7eJ8FLFxr1/Jb+Yq4" + STATIC_ROOT = os.path.join(BASE_DIR, 'static/') STATIC_URL = os.getenv("STATIC_URL", '/static/') STATICFILES_FINDERS = [ @@ -184,7 +199,7 @@ STATICFILES_FINDERS = [ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') -MEDIA_URL = os.getenv("MEDIA_URL", '/media/') +MEDIA_URL = os.getenv("MEDIA_URL", "https://ojuso-media.nyc3.digitaloceanspaces.com/ojuso-media/") # Cache # https://docs.djangoproject.com/en/1.11/topics/cache/ @@ -248,3 +263,13 @@ RAVEN_CONFIG = { # release based on the git info. 'release': raven.fetch_git_sha(os.path.dirname(os.pardir)), } + +# Avatars +AVATAR_GRAVATAR_DEFAULT = "mm" +AVATAR_CLEANUP_DELETED = True + +# Messages +from django.contrib.messages import constants as messages +MESSAGE_TAGS = { + messages.ERROR: 'danger' +} \ No newline at end of file diff --git a/ojusomap/templates/auth/user_detail.html b/ojusomap/templates/auth/user_detail.html new file mode 100644 index 0000000..892fd9f --- /dev/null +++ b/ojusomap/templates/auth/user_detail.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block title %}User: {{ object.username }}{% endblock %} + +{% block content %} +
+ +
+
+ +

{{ object.username }}

+ {% if object.name %} +

{{ object.name }}

+ {% endif %} +
+
+ +{% if object == request.user %} + +
+ + + +
+ +{% endif %} + + +
+{% endblock content %} \ No newline at end of file diff --git a/ojusomap/templates/auth/user_form.html b/ojusomap/templates/auth/user_form.html new file mode 100644 index 0000000..51f2d6d --- /dev/null +++ b/ojusomap/templates/auth/user_form.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% load crispy_forms_tags %} + +{% block title %}{{ user.username }}{% endblock %} + +{% block content %} +

{{ user.username }}

+
+ {% csrf_token %} + {{ form|crispy }} +
+
+ +
+
+
+{% endblock %} \ No newline at end of file diff --git a/ojusomap/templates/auth/user_list.html b/ojusomap/templates/auth/user_list.html new file mode 100644 index 0000000..9d4761a --- /dev/null +++ b/ojusomap/templates/auth/user_list.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% load static i18n %} +{% block title %}Members{% endblock %} + +{% block content %} +
+

Users

+ +
+ {% for user in user_list %} + +

{{ user.username }}

+
+ {% endfor %} +
+
+{% endblock content %} \ No newline at end of file diff --git a/ojusomap/templates/base.html b/ojusomap/templates/base.html index b14f3f5..c90c297 100644 --- a/ojusomap/templates/base.html +++ b/ojusomap/templates/base.html @@ -1,4 +1,5 @@ {% spaceless %} +{% load avatar_tags %} {% load flatpages %} {% load i18n %} {% load leaflet_tags %} @@ -10,7 +11,9 @@ {% block page_title %}Ojuso{% endblock %} - + + + {# Additional Stylesheets #} {% block stylesheets %} @@ -22,42 +25,68 @@ - + + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} {% block content %} {% endblock %} {# CDN Javascript #} - + + + {% block scripts %}{% endblock %} {% endspaceless %} diff --git a/ojusomap/templates/registration/activation_email.html b/ojusomap/templates/registration/activation_email.html index 52852e8..d32fbb1 100644 --- a/ojusomap/templates/registration/activation_email.html +++ b/ojusomap/templates/registration/activation_email.html @@ -1,10 +1,9 @@ {% extends 'base_email.html' %} {% block content %}
- {{user}},

- To activate your account and log in, click here.
- This link will expire in 7 days so be sure do it soon.

- See you on the activist trail!

- - Animal Rights Map + {{user}},

+ To activate your account and log in, click here.
+ This link will expire in 7 days so be sure do it soon.

+ - Ojuso
{% endblock %} diff --git a/ojusomap/templates/registration/login.html b/ojusomap/templates/registration/login.html index 7ccabe3..f66d577 100644 --- a/ojusomap/templates/registration/login.html +++ b/ojusomap/templates/registration/login.html @@ -9,8 +9,8 @@ {% block content %}
-

Login

-

{% trans "Welcome!" %}

+

{% trans 'Login' %}

+

{% trans 'Welcome!' %}

{% bootstrap_messages %}
diff --git a/ojusomap/templates/registration/registration_form.html b/ojusomap/templates/registration/registration_form.html index 49b1be3..50207f9 100644 --- a/ojusomap/templates/registration/registration_form.html +++ b/ojusomap/templates/registration/registration_form.html @@ -2,21 +2,22 @@ {% load bootstrap3 %} {% load crispy_forms_tags %} {% load envelope_tags %} +{% load i18n %} {% block page_name %}Registration{% endblock %} {% block content %}
-

Registration

-

Create your account and get active!

+

{% trans 'Registration' %}

+

{% trans 'Create your account and get active!' %}

{% bootstrap_messages %} {% csrf_token %} {% antispam_fields %} {{ form|crispy }} - +
{% endblock %} diff --git a/ojusomap/urls.py b/ojusomap/urls.py index 41d1dd7..bc6b0fc 100644 --- a/ojusomap/urls.py +++ b/ojusomap/urls.py @@ -51,12 +51,13 @@ apirouter.register(r'case-studies', CaseStudyViewSet) urlpatterns = [ url(r'api/', include(apirouter.urls)), url(r'^admin/', admin.site.urls), + url(r'^avatar/', include('avatar.urls')), url(r'^cas/', include('cas_server.urls', namespace='cas_server')), - # url(r'^contact/', include('apps.contact.urls'), name="contact") + # url(r'^contact/', include('apps.contact.urls'), name="contact"), ] urlpatterns += i18n_patterns( - url(r'^accounts/profile/', include('apps.profiles.urls')), + url(r'^accounts/profile/', include('apps.profiles.urls', namespace="profile")), # url(r'^accounts/logout/?$', include('django.contrib.auth.views.logout'), {'next_page': '/'}), url(r'^accounts/', include('registration.backends.default.urls')), url(r'', include('apps.map.urls'), name="map"), diff --git a/requirements.txt b/requirements.txt index 39c5f06..058670b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,10 @@ appdirs==1.4.3 brotlipy==0.7.0 +boto==2.48.0 +boto3==1.4.7 Django==1.11.6 django-appconf==1.0.2 +django-avatar==4.0.1 django-bootstrap3==8.2.3 django-braces==1.11.0 django-cas-server==0.8.0 @@ -15,6 +18,7 @@ django-geojson==2.10.0 -e git://github.com/makinacorpus/django-leaflet.git@a43acc5fed6674b413a6fab0feeb7c44e67c2ca8#egg=django-leaflet django-moderation==0.5.0 django-registration-redux==1.6 +django-storages==1.6.5 djangorestframework==3.6.3 djangorestframework-gis==0.11.2 gunicorn==19.7.1