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 multiselectfield import MultiSelectField from . import validators class Shapefile(models.Model): file = models.FileField( upload_to='shapefiles/', ) class CaseStudy(models.Model): """Model for case studies submitted to the Ojuso Platform""" # Choice lists for drop-downs SECTOR_CHOICES = ( (_('Renewable Energy Generation'), ( ('WND', _('Wind')), ('SOL', _('Solar')), ('HYD', _('Hydro')), )), ('PG', _('Power Grids')), ('SM', _('Supply of Minerals')), ) POSITIVE_NEGATIVE_CHOICES = ( ('P', _('Positive')), ('N', _('Negative')) ) LAND_OWNERSHIP_CHOICES = ( ('PRI', _('Private Land')), ('PUB', _('Public Land')), ('COM', _('Community Land')), ('OTH', _('Other')), ) LOCATION_CONTEXT_CHOICES = ( ('RUR', _('Rural')), ('URB', _('Urban')), ) TYPE_OF_ECOSYSTEM_CHOICES = ( (_('Water Based'), ( ('MARINE', _('Marine (e.g. Ocean, Sea)')), ('FRESH', _('Freshwater (e.g. Freshwater, Lake)')), )), (_('Land Based'), ( ('FOREST', _('Forest/Jungle')), ('AGRI', _('Agricultural Land')), ('GRASS', _('Grassland')), ('DESERT', _('Desert (Tundra, Ice or Sand)')), ('WETLND', _('Wetland (Marsh, Mangrove, Peat Soil)')), ('URBAN', _('Urban')), )) ) PROJECT_STATUS_CHOICES = ( ('EXSTNG', _('Existing Project')), ('UCONST', _('Under Construction')), ('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')) ) TYPE_OF_EXTRACTION_CHOICES = ( ('SUR', _('Surface (open pit/open cast/open cut mining')), ('SUB', _('Sub-surface (underground mining)')), ('SEA', _('Seabed mining')), ('URB', _('Urban mining/recycling')) ) MINERAL_COMMODITY_CHOICES = ( ('ALU', _('Aluminium (Bauxite)')), ('ARS', _('Arsenic')), ('BER', _('Beryllium')), ('CAD', _('Cadmium')), ('CHR', _('Chromium')), ('COK', _('Coking')), ('COA', _('Coal (for steel)')), ('COP', _('Copper')), ('GAL', _('Gallium')), ('GER', _('Germanium')), ('GLD', _('Gold')), ('HRE', _('Heavy Rare Earth Elements (Gadolinium, Terbium, Dysprosium, Holmium, Erbium, Thulium, Ytterbium, Lutetium, Yttrium, Scandium)')), ('IRN', _('Iron')), ('LRE', _('Light Rare Earth Elements (Lanthanum, Cerium, Praseodymium, Neodymium, Promethium, Samarium, Europium)')), ('LED', _('Lead')), ('LIT', _('Lithium')), ('MAN', _('Manganese')), ('MER', _('Mercury')), ('MOL', _('Molybdenum')), ('NIC', _('Nickel')), ('NIO', _('Niobium')), ('PGM', _('Platinum group metals (ruthenium, rhodium, palladium, osmium, iridium, and platinum)')), ('RHE', _('Rhenium')), ('SIL', _('Silicon')), ('SIV', _('Silver')), ('TAN', _('Tantalum')), ('TEL', _('Tellurium')), ('THA', _('Thallium')), ('TIN', _('Tin')), ('TIT', _('Titanium')), ('TUN', _('Tungsten')), ('VAN', _('Vanadium')), ('ZNC', _('Zinc')), ('OTR', _('Other')) ) USE_IN_ENERGY_ECONOMY_CHOICES = ( ('WTM', _('Wind turbine manufacturing')), ('SPM', _('Solar panel manufacturing')), ('STM', _('Solar thermal system manufacturing')), ('HGM', _('Hydropower generator manufacturing')), ('GGM', _('Geothermal generator manufacturing')), ('ESS', _('Energy storage (inc. battery systems)')), ('OTR', _('Others')) ) POSITIVE_CASE_TYPE_CHOICES = ( ('CREP', _('Community renewable energy project')), ('EACP', _('Energy as a commons project')), ('PSEP', _('Public/state (federal, state, municipal) energy project')), ('CORS', _('A case of responsible sourcing/supply chain/lifecycle management')), ) NEGATIVE_CASE_REASONS_CHOICES = ( ('VOLR', _('Violation of land rights')), ('VOHR', _('Violation of fundamental human rights, indigenous rights and/or other collective rights')), ('EIMP', _('Environmental impacts (severe impacts on ecosystems / violation of laws, plans or programs of \ environmental conservation or territorial governance systems etc.')), ('NCUL', _('Negative cultural impacts (erosion/destruction of bio-cultural heritage, impacts on sacred land \ etc)')), ('AGGR', _('Aggression/threats to community members opposed to the project, collaboration with organized crime \ etc')), ('ALAB', _('Abusive labour practices')), ('CRUP', _('Corruption and/or irregular permitting or contracting, conflicts of interest etc')), ('OTHR', _('Other reasons')) ) # 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 ## # User who submitted case study author = models.ForeignKey( User, models.SET_NULL, blank=True, null=True, editable=False ) # Date and time of submission date_created = models.DateTimeField(auto_now_add=True, null=False) # Slug derived from entry_name, used in urls for SEO slug = AutoSlugField(populate_from=['entry_name'], editable=False) ## # First Screen - Basic information ## # 1.1 entry_name = models.CharField( verbose_name=_("Entry Name"), help_text=_("Enter the name of the entry. This should usually be the\ name of project."), max_length=128 ) # N/A - Not explicitly listed in spec 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( verbose_name=_("Sector of economy"), help_text=_("Which sector of the renewable energy economy is most\ relevant?"), max_length=3, choices=SECTOR_CHOICES ) # 1.3 positive_or_negative = models.CharField( verbose_name=_("Positive or negative?"), help_text=_("Is the case study a positive case or a negative case?"), max_length=1, choices=POSITIVE_NEGATIVE_CHOICES ) # 1.4 country = CountryField( verbose_name=_("Country field"), help_text=_("Select the country of the project") ) # 1.5.1 area_of_land = models.IntegerField( verbose_name=_("Approximate land area"), help_text=_("The area of land covered by the project (in km²)") ) # 1.5.2 land_ownership = models.CharField( verbose_name=_("Land ownership"), help_text=_("What type of ownership does the land fall under?"), max_length=3, choices=LAND_OWNERSHIP_CHOICES ) # 1.5.3 land_ownership_details = models.CharField( verbose_name=_("Land ownership details"), help_text=_("Please specify details about land ownership if you chose 'other'"), max_length=256, null=True, blank=True, ) # 1.5.4 location_context = models.CharField( verbose_name=_("Location"), help_text=_("Select the context that is most applicable to this case\ study."), max_length=3, choices=LOCATION_CONTEXT_CHOICES ) # 1.5.5 type_of_ecosystem = models.CharField( verbose_name=_("Type of ecosystem"), help_text=_("Select the most relevant type of ecosystem."), max_length=6, choices=TYPE_OF_ECOSYSTEM_CHOICES, default=None, null=True, blank=True ) # 1.5.5.3 describe_ecosystem = models.TextField( verbose_name=_("Describe the ecosystem"), help_text=_("In your own words, add more detail about the ecosystem."), ) # 1.5.6 affects_indigenous = models.BooleanField( verbose_name=_("Affects indigenous people?"), help_text=_("Does the project affect indigenous communities?") ) # 1.5.6.1 affects_indigenous_detail = models.CharField( verbose_name=_("Affects Indigenous - Details"), help_text=_("What group of indigenous people does the community belong\ to?"), max_length=256, default=None, null=True, blank=True ) # 1.6 project_status = models.CharField( verbose_name=_("Status of Project"), help_text=_("What is the status of the current project?"), max_length=6, 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"), help_text=_("Briefly describe the project. This will be displayed at\ the top of the case study page. Maximum 500 chars (about \ 3½ tweets)") ) # 1.10 full_description = models.TextField( verbose_name=_("Full Description"), help_text=_("Describe the project in full. Separate paragraphs with a\ new line Please add as much detail as you feel is necessary\ here.") ) # 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.15.2 image_caption = models.CharField( verbose_name=_("Image caption"), max_length=500, default=None, null=True, ) # 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 \ 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 - Technical and economic analysis ## # 2.1 - Renewable Energy Generation # 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=_("Description of feedstock"), 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 # TODO: Auto-completion based on previous entries so we can query case-studies with the same answer. generation_equipment_supplier = models.TextField( verbose_name=_("Generation equipment supplier"), help_text=_("Enter the supplier of the generation equipment. (E.g. Siemens)"), default=None, null=True, blank=True ) # 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 - Power Grids / Energy Storage # 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 ) # 2.2.1.4 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 ) # 2.2.2 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 ) # 2.2.2.1 maximum_power_output = models.BigIntegerField( verbose_name=_('Maximum power output'), help_text=_('Enter the maximum power output of the storage system in Watts (W). (W=J/s)'), default=None, null=True, blank=True ) # 2.2.2.2 discharge_time = models.BigIntegerField( verbose_name=_('Time for discharge from full capacity'), help_text=_('Enter the time it takes to discharge from full capacity at maximum power output (in seconds) \ (1h=3600s)'), default=None, null=True, blank=True ) # 2.2.3 contractor_or_supplier_of_technology = models.CharField( verbose_name=_('Contractor and/or supplier of technology'), help_text=_('List companies that act as contractors or suppliers of technology related to energy storage.'), max_length=256, default=None, null=True, blank=None ) # 2.2.4 approximate_total_investment = models.PositiveIntegerField( verbose_name=_('Approximate total investment'), help_text=_('Enter the approximate total investment in USD ($).'), default=None, null=True, blank=None ) # 2.2.5 additional_technical_details = models.CharField( verbose_name=_("Additional technical or economic details"), help_text=_("Add any additional details such as: length, from-to, voltage, substations etc"), max_length=512, default=None, null=True, blank=True ) # 2.3.1.1 minerals_or_commodities = models.CharField( verbose_name=_("Mineral commodity/commodities"), help_text=_("Select the mineral commodity that is primarily mined in this project"), max_length=3, choices=MINERAL_COMMODITY_CHOICES, default=None, null=True, blank=True ) # 2.3.1.2 minerals_or_commodities_other = models.CharField( verbose_name=_("Other mineral commodity"), help_text=_("Enter the mineral commodity that isn't in the list."), max_length=64, default=None, null=True, blank=True ) # 2.3.2.1 use_in_energy_economy = models.CharField( verbose_name=_("Potential user in renewable energy economy"), help_text=_("Select the potential use of the minerals in the renewable energy economy"), max_length=3, choices=USE_IN_ENERGY_ECONOMY_CHOICES, default=None, null=True, blank=True ) # 2.3.2.9 use_in_energy_economy_other = models.CharField( verbose_name=_('Other use in energy economy'), max_length=128, default=None, null=True, blank=True ) # 2.3.3.1 project_life_span = models.CharField( verbose_name=_("Project life span"), help_text=_("e.g. 12 years of production, 15 years overall"), max_length=200, default=None, null=True, blank=None ) # 2.3.3.2 size_of_concessions = models.CharField( verbose_name=_("Size of concessions"), help_text=_("Describe the size of concession(s) granted to company/companies (e.g. 'one concession encompassing\ 2,300 hectares')"), max_length=200, default=None, null=True, blank=None ) # 2.3.3.3 projected_production_of_commodities = models.CharField( verbose_name=_("Projected production of key commodities"), help_text=_("Describe the projected production of commodities per annum and overall (e.g. '40 million tonnes of\ iron ore per year, 200 million tonnes over 5 year life of mine'"), max_length=256, default=None, null=True, blank=None ) # 2.3.4 type_of_extraction = models.CharField( verbose_name=_("Type of extraction"), max_length=2, choices=TYPE_OF_EXTRACTION_CHOICES, default=None, null=True, blank=True ) # 2.3.5 associated_infrastructure = models.CharField( verbose_name=_("Associated infrastructure in the locality"), help_text=_("List any associated infrastructure in the locality (e.g. tailings dams/mine waste storage and \ treatment facilities; ore processing facilities; smelting facilities; hydroelectric dams/energy infrastructure;\ transport infrastructure e.g. roads or rail."), max_length=256, default=None, null=True, blank=True ) ## # Third Screen - Socio-economic analysis ## # 3.1.1 positive_case_type = models.CharField( verbose_name=_('What kind of positive case is this entry about?'), help_text=_('Select the most relevant type of positive case'), choices=POSITIVE_CASE_TYPE_CHOICES, max_length=4, default=None, null=True, blank=True, ) # 3.1.2 socioeconomic_benefits = models.TextField( verbose_name=_('Socio-economic benefits'), help_text=_('Please expand on your response given in the full description on page one. We would expect \ benefits to go beyond emissions savings, paying rent for land, or complying with environmental or social \ legislation'), default=None, null=True, blank=True ) # 3.1.3 + 3.2.5 key_actors_involved = models.CharField( verbose_name=_('Key actors involved (individual/organisational)'), max_length=256, default=None, null=True, blank=True ) # 3.1.4 project_status_detail = models.TextField( verbose_name=_('Current status of the project'), help_text=_("Describe the current status of the project, expanding beyond 'existing', 'under construction' etc"), default=None, null=True, blank=True ) # 3.1.5 obstacles_and_hindrances = models.CharField( verbose_name=_('Obstacles and hindrances'), help_text=_('List any obstacles or hindrances experienced in the course of the project'), max_length=512, default=None, null=True, blank=True ) # 3.1.6 identified_partnerships = models.CharField( verbose_name=_('Identified partnerships'), help_text=_('Are you looking for partnerships or have any clearly identified need? If so, please describe it \ here.'), max_length=256, default=None, null=True, blank=True ) # 3.1.7.1 + 3.2.8.1 # 3.1.7.2 + 3.2.8.1 # 3.1.7.3 + 3.2.8.1 # 3.1.7.4 + 3.2.8.1 # 3.1.7.5 + 3.2.8.1 # 3.1.7.6 + 3.2.8.1 # 3.2.1 negative_case_reasons = MultiSelectField( choices=NEGATIVE_CASE_REASONS_CHOICES, default=None, null=True, blank=True ) # 3.2.1.9 negative_case_reasons_other = models.CharField( verbose_name=_("Other reason for negative case"), help_text=_("Please include other reasons, noting that we aim to focus on projects with substantive negative \ impacts on vulnerable groups."), max_length=512, default=None, null=True, blank=True ) # 3.2.2 negative_socioenvironmental_impacts = models.TextField( verbose_name=_("Describe the negative socio-environmental impacts"), help_text=_("Provide a detailed description of the negative socio-environmental impacts (please provide all \ relevant details, such as type of ecosystem and presence of any existing reserve in the area, \ , specific communities affected by the project, total geographic footprint of the project, and \ tenure system affected in the case of land grabs, kind of permits that were irregularly issued if \ this is the case."), default=None, null=True, blank=True ) # 3.2.3 isolated_or_widespread = models.TextField( verbose_name=_("Describe if the project is isolated or commonplace."), help_text=_("Is this an isolated project or are there similar projects in the same geographic area? If there \ are more, can you describe them? Are there any significant cumulative synergistic effects?"), default=None, null=True, blank=True ) # 3.2.4.1 when_did_organising_start = models.CharField( verbose_name=_("When did local organising efforts begin?"), help_text=_("Before the project started? During project implementation? After project implementation? \ Describe in your own words."), max_length=512, default=None, null=True, blank=True ) # 3.2.4.2 who_has_been_involved = models.CharField( verbose_name=_("Which communities, groups and organisations have been involved?"), max_length=512, default=None, null=True, blank=True ) # 3.2.4.3 participation_mechanisms = models.CharField( verbose_name=_("What mechanisms of participation have been used?"), help_text=_("e.g. direct action, local referendums, legal cases, letters or petitions etc"), max_length=512, default=None, null=True, blank=True ) # 3.2.6 potential_partnerships = models.CharField( verbose_name=_("Describe potential partnerships"), help_text=_("Are you looking for partnerships or do you have any clearly identified need? If so, please \ describe it here."), max_length=512, default=None, null=True, blank=True ) # 3.2.7 wants_conversation_with_ojuso = models.NullBooleanField( verbose_name=_("Would you like to have a conversation with the ojuso team?"), help_text=_("This would be a conversation about challenging or engaging related developers, companies and \ investors."), default=None, null=True, blank=True ) ## # Fourth Screen - Uploads ## # 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.1 shapefiles = models.FileField( verbose_name=_("Shapefiles"), help_text=_("If you have territory that you would like to show in relation to this project - e.g. Bienes \ Comunales de Ixtepec etc. This is a set of 3 or more (often 5-6) files with file extensions like \ .cpg, .dbf, .prj, .qpj, .shp, .shx"), default=None, null=True, blank=True ) # 4.3.2 coordinate_reference_system = models.CharField( verbose_name=_("Coordinate reference system"), help_text=_("Enter the coordinate reference system of the shapefiles."), max_length=12, default=None, null=True, blank=True ) # 4.3.3 name_of_territory_or_area = models.CharField( verbose_name=_("Name of territory or area"), 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) def clean(self, *args, **kwargs): """Perform validation on the model as a whole and throw a ValidationError if anything isn't how it should be.""" pass def save(self, *args, **kwargs): """Override the save method to create a slug when the model is created. Slug is only created and never modified as we are basing our URLs on it and don't want it to change - ever.""" if not self.pk: # Newly created object, so set slug 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]