class RecipientRegion(models.Model): project = models.ForeignKey( 'Project', verbose_name=_(u'project'), related_name='recipient_regions' ) region = ValidXMLCharField( _(u'region'), blank=True, max_length=3, choices=codelist_choices(REGION) ) region_vocabulary = ValidXMLCharField( _(u'region vocabulary'), blank=True, max_length=1, choices=codelist_choices(REGION_VOCABULARY) ) percentage = models.DecimalField( _(u'percentage'), blank=True, null=True, max_digits=4, decimal_places=1, validators=[MaxValueValidator(100), MinValueValidator(0)] ) text = ValidXMLCharField( _(u'region description'), blank=True, max_length=50, help_text=_(u'(max 50 characters)') ) def iati_region(self): return codelist_value(Region, self, 'region') def iati_vocabulary(self): return codelist_value(RegionVocabulary, self, 'region_vocabulary') class Meta: app_label = 'rsr' verbose_name = _(u'recipient region') verbose_name_plural = _(u'recipient regions')
class PlannedDisbursement(models.Model): project = models.ForeignKey('Project', verbose_name=_(u'project'), related_name='planned_disbursements') value = models.DecimalField(_(u'value'), blank=True, max_digits=10, decimal_places=2) value_date = models.DateField(_(u'value date'), null=True, blank=True) currency = ValidXMLCharField(_(u'currency'), blank=True, max_length=3, choices=codelist_choices(CURRENCY)) updated = models.DateField(_(u'updated'), null=True, blank=True) period_start = models.DateField(_(u'period start'), null=True, blank=True) period_end = models.DateField(_(u'period end'), null=True, blank=True) type = ValidXMLCharField(_(u'type'), blank=True, max_length=1, choices=codelist_choices(BUDGET_TYPE)) def __unicode__(self): return self.value def iati_currency(self): return codelist_value(Currency, self, 'currency') def iati_type(self): return codelist_value(BudgetType, self, 'type') class Meta: app_label = 'rsr' verbose_name = _(u'planned disbursement') verbose_name_plural = _(u'planned disbursements')
class HumanitarianScope(models.Model): project = models.ForeignKey('Project', verbose_name=_('project'), related_name='humanitarian_scopes') code = ValidXMLCharField( _('humanitarian scope code'), blank=True, max_length=25, help_text=_('A code for the event or action from the vocabulary specified. More ' 'information on the vocabularies can be found here: ' '<a href="http://glidenumber.net/glide/public/search/search.jsp" ' 'target="_blank">Glide</a> and ' '<a href="http://fts.unocha.org/docs/IATICodelist_HS2-1.csv" ' 'target="_blank">Humanitarian plan</a>.')) type = ValidXMLCharField( _('humanitarian scope type'), blank=True, max_length=1, choices=codelist_choices(HUMANITARIAN_SCOPE_TYPE), help_text=_('The type of event or action being classified. See the ' '<a href="http://iatistandard.org/202/codelists/HumanitarianScopeType/" ' 'target="_blank">IATI codelist</a>.')) vocabulary = ValidXMLCharField( _('humanitarian scope vocabulary'), blank=True, max_length=3, choices=codelist_choices(HUMANITARIAN_SCOPE_VOCABULARY), help_text=_('A recognised vocabulary of terms classifying the event or action. See the ' '<a href="http://iatistandard.org/202/codelists/HumanitarianScopeVocabulary/" ' 'target="_blank">IATI codelist</a>.')) vocabulary_uri = ValidXMLCharField( _('humanitarian scope vocabulary URI'), blank=True, max_length=1000, help_text=_('If the vocabulary is 99 (reporting organisation), the URI where this ' 'internal vocabulary is defined.')) text = ValidXMLCharField(_('humanitarian scope description'), blank=True, max_length=1000, help_text=_('Optionally enter a description.')) class Meta: app_label = 'rsr' verbose_name = _('humanitarian scope') verbose_name_plural = _('humanitarian scopes') ordering = ('pk',) def __str__(self): if self.text: return self.text elif self.code: return self.code else: return '' def iati_type(self): return codelist_value(HumanitarianScopeType, self, 'type') def iati_type_unicode(self): return str(self.iati_type()) def iati_vocabulary(self): return codelist_value(HumanitarianScopeVocabulary, self, 'vocabulary') def iati_vocabulary_unicode(self): return str(self.iati_vocabulary())
def test_codelist_choices_false(self): """ Test calling codelist_choices(<codelist>, False) """ # snippet of FINANCE_TYPE codelist codelist_1 = ( ("code", "name", "description"), ("1", "Global", "The activity scope is global"), ("2", "Regional", "The activity scope is a supranational region"), ("3", "Multi-national", "The activity scope covers multiple countries, that don't constitute a region"), ("4", "National", "The activity scope covers one country"), ) choices_1 = [ ("1", "Global",), ("2", "Regional",), ("3", "Multi-national",), ("4", "National",), ] codelist_2 = ( ("category", "code", "name"), ("100", "110", "Standard grant"), ("100", "111", "Subsidies to national private investors"), ("100", "210", "Interest subsidy"), ("200", "211", "Interest subsidy to national private exporters"), ) choices_2 = [ ("110", "Standard grant"), ("111", "Subsidies to national private investors"), ("210", "Interest subsidy"), ("211", "Interest subsidy to national private exporters"), ] codelist_3 = ( ("category", "code"), ("application", "application/1d-interleaved-parityfec"), ("application", "application/3gpdash-qoe-report+xml"), ("application", "application/3gpp-ims+xml"), ("application", "application/A2L"), ) choices_with_code_3 = [ ("application/1d-interleaved-parityfec", "application/1d-interleaved-parityfec"), ("application/3gpdash-qoe-report+xml", "application/3gpdash-qoe-report+xml"), ("application/3gpp-ims+xml", "application/3gpp-ims+xml"), ("application/A2L", "application/A2L"), ] generated_choices_1 = codelist_choices(codelist_1, False) self.assertEqual(choices_1, generated_choices_1) generated_choices_2 = codelist_choices(codelist_2, False) self.assertEqual(choices_2, generated_choices_2) generated_choices_with_code_3 = codelist_choices(codelist_3) self.assertEqual(choices_with_code_3, generated_choices_with_code_3)
def test_codelist_choices_true(self): """ Test calling codelist_choices(<codelist>, True) """ # snippet of FINANCE_TYPE codelist codelist_1 = ( (u"code", u"name", u"description"), (u"1", u"Global", u"The activity scope is global"), (u"2", u"Regional", u"The activity scope is a supranational region"), (u"3", u"Multi-national", u"The activity scope covers multiple countries, that don't constitute a region" ), (u"4", u"National", u"The activity scope covers one country"), ) choices_with_code_1 = [ ( u"1", u"1 - Global", ), ( u"2", u"2 - Regional", ), ( u"3", u"3 - Multi-national", ), ( u"4", u"4 - National", ), ] codelist_2 = ( (u"category", u"code", u"name"), (u"100", u"110", u"Standard grant"), (u"100", u"111", u"Subsidies to national private investors"), (u"100", u"210", u"Interest subsidy"), (u"200", u"211", u"Interest subsidy to national private exporters"), ) choices_with_code_2 = [ (u"110", u"110 - Standard grant"), (u"111", u"111 - Subsidies to national private investors"), (u"210", u"210 - Interest subsidy"), (u"211", u"211 - Interest subsidy to national private exporters"), ] generated_choices_with_code_1 = codelist_choices(codelist_1) self.assertEqual(choices_with_code_1, generated_choices_with_code_1) generated_choices_with_code_2 = codelist_choices(codelist_2) self.assertEqual(choices_with_code_2, generated_choices_with_code_2)
class OrganisationRegionBudget(OrganisationBudget): organisation = models.ForeignKey('Organisation', on_delete=models.CASCADE, verbose_name=_('organisation'), related_name='recipient_region_budgets') region = ValidXMLCharField( _('recipient region'), blank=True, max_length=25, choices=codelist_choices(REGION), help_text=_( 'This identifies the region which concerns the organisation budget.' )) region_vocabulary = ValidXMLCharField( _('vocabulary'), blank=True, max_length=2, choices=codelist_choices(REGION_VOCABULARY), help_text=_( 'The vocabulary from which the region code is drawn. If it is not present 1 – ' '\'OECD DAC\' is assumed.')) region_vocabulary_uri = ValidXMLCharField( _('vocabulary URI'), blank=True, max_length=1000, help_text=_( 'If the vocabulary is 99 (reporting organisation), the URI where this ' 'internal vocabulary is defined.')) text = ValidXMLCharField( _('description'), blank=True, max_length=100, help_text=_('Optionally enter a short description.')) class Meta: app_label = 'rsr' verbose_name = _('organisation recipient region budget') verbose_name_plural = _('organisation recipient region budgets') def iati_region(self): return codelist_value(Region, self, 'region') def iati_region_unicode(self): return str(self.iati_region()) def iati_region_vocabulary(self): return codelist_value(RegionVocabulary, self, 'region_vocabulary') def iati_region_vocabulary_unicode(self): return str(self.iati_region_vocabulary())
class TransactionSector(models.Model): project = models.ForeignKey('Transaction', verbose_name=_(u'transaction'), related_name='sectors') code = ValidXMLCharField(_(u'sector'), blank=True, max_length=5) text = ValidXMLCharField(_(u'description'), blank=True, max_length=100, help_text=_(u'(max 100 characters)')) vocabulary = ValidXMLCharField(_(u'vocabulary'), blank=True, max_length=5, choices=codelist_choices(SECTOR_VOCABULARY)) def iati_sector(self): if self.code and (self.vocabulary == '1' or self.vocabulary == 'DAC'): return codelist_value(Sector, self, 'code') elif self.code and (self.vocabulary == '2' or self.vocabulary == 'DAC-3'): return codelist_value(SectorCategory, self, 'code') else: return self.code def iati_vocabulary(self): return codelist_value(SectorVocabulary, self, 'vocabulary') class Meta: app_label = 'rsr' verbose_name = _(u'transaction sector') verbose_name_plural = _(u'transaction sectors') unique_together = ('project', 'vocabulary')
class CrsAddOtherFlag(models.Model): """ Other flag of CRS++ reporting. """ crs = models.ForeignKey('CrsAdd', verbose_name=u'crs', related_name='other_flags') code = ValidXMLCharField( _(u'code'), max_length=1, choices=codelist_choices(C_R_S_ADD_OTHER_FLAGS), help_text=_(u'An IATI code describing the equivalent CRS++ columns. See the <a ' u'href="http://iatistandard.org/202/codelists/CRSAddOtherFlags/" ' u'target="_blank">IATI codelist</a>.') ) significance = models.NullBooleanField( _(u'significance'), blank=True, help_text=_(u'Indicate whether the flag applies or not.') ) def __unicode__(self): if self.code: try: return self.iati_code().name except AttributeError: return self.iati_code() else: return u'%s' % _(u'No other flag code specified') def iati_code(self): return codelist_value(CRSAddOtherFlags, self, 'code') def iati_code_unicode(self): return str(self.iati_code()) class Meta: app_label = 'rsr' verbose_name = _(u'CRS other flag') verbose_name_plural = _(u'CRS other flags')
class FssForecast(models.Model): """ Forecast items for an OECD DAC Forward Spending Survey item. """ fss = models.ForeignKey('Fss', verbose_name=_(u'fss'), related_name='forecasts') year = models.PositiveIntegerField(_(u'year'), blank=True, null=True, max_length=4) value_date = models.DateField(_(u'value date'), blank=True, null=True) currency = ValidXMLCharField(_(u'currency'), blank=True, max_length=3, choices=codelist_choices(CURRENCY)) value = models.DecimalField(_(u'interest received'), max_digits=10, decimal_places=2, blank=True, null=True) def iati_currency(self): return codelist_value(Currency, self, 'loan_status_currency') class Meta: app_label = 'rsr' verbose_name = _(u'FSS forecast') verbose_name_plural = _(u'FSS forecasts')
class RelatedProject(models.Model): project = models.ForeignKey('Project', related_name='related_projects') related_project = models.ForeignKey('Project', related_name='related_to_projects', null=True, blank=True, on_delete=models.SET_NULL) related_iati_id = ValidXMLCharField( _(u'related project IATI identifier'), max_length=100, blank=True, help_text=_( u'The IATI Identifier for the related project.<br>' u'Fill this in if the related project does not exist in RSR')) relation = ValidXMLCharField( _(u'relation'), max_length=1, choices=codelist_choices(RELATED_ACTIVITY_TYPE), help_text=_( u'The relation between a project and related project. ' u'(E.g. select the \'Parent\' relation when the selected project here is ' u'the parent of this project).')) def iati_relation(self): return codelist_value(RelatedActivityType, self, 'relation') class Meta: app_label = 'rsr' verbose_name = _(u'related project') verbose_name_plural = _(u'related projects') ordering = [ 'project', ]
class ProjectDocumentCategory(models.Model): document = models.ForeignKey(ProjectDocument, on_delete=models.CASCADE, related_name='categories', verbose_name=_('document')) category = ValidXMLCharField(_('document category'), max_length=3, blank=True, choices=codelist_choices(DOCUMENT_CATEGORY), help_text=_('The description of the type of content contained ' 'within the document.')) class Meta: app_label = 'rsr' verbose_name = _('project document category') verbose_name_plural = _('project document categories') ordering = ['-id', ] def __str__(self): if self.category: try: return self.iati_category().name except AttributeError: return self.iati_category() else: return '%s' % _('No category specified') def iati_category(self): return codelist_value(DocumentCategory, self, 'category') def iati_category_unicode(self): return str(self.iati_category())
class CountryBudgetItem(models.Model): project = models.ForeignKey('Project', verbose_name=_(u'project'), related_name='country_budget_items') code = ValidXMLCharField(_(u'budget item'), max_length=6, blank=True, choices=codelist_choices(BUDGET_IDENTIFIER)) description = ValidXMLCharField(_(u'description'), max_length=100, blank=True, help_text=_(u'(max 100 characters)')) percentage = models.DecimalField( _(u'percentage'), blank=True, null=True, max_digits=4, decimal_places=1, validators=[MaxValueValidator(100), MinValueValidator(0)]) def iati_code(self): return codelist_value(BudgetIdentifier, self, 'code') class Meta: app_label = 'rsr' verbose_name = _(u'country budget item') verbose_name_plural = _(u'country budget items')
class OrganisationDocumentCountry(models.Model): document = models.ForeignKey(OrganisationDocument, related_name='countries', verbose_name=_('document')) country = ValidXMLCharField( _('recipient country'), blank=True, max_length=2, choices=codelist_choices(COUNTRY, show_code=False), help_text=_('This identifies the country which concerns the organisation document.') ) text = ValidXMLCharField( _('description'), blank=True, max_length=100, help_text=_('Optionally enter a short description.') ) class Meta: app_label = 'rsr' verbose_name = _('document country') verbose_name_plural = _('document countries') ordering = ['-id', ] def __str__(self): if self.country: try: return self.iati_country().name except AttributeError: return self.iati_country() else: return '%s' % _('No country specified') def iati_country(self): return codelist_value(Country, self, 'country') def iati_country_unicode(self): return str(self.iati_country())
class OrganisationCountryBudget(OrganisationBudget): organisation = models.ForeignKey('Organisation', verbose_name=_('organisation'), related_name='recipient_country_budgets') country = ValidXMLCharField( _('recipient country'), blank=True, max_length=2, choices=codelist_choices(COUNTRY, show_code=False), help_text= _('This identifies the country which concerns the organisation budget.' )) text = ValidXMLCharField( _('description'), blank=True, max_length=100, help_text=_('Optionally enter a short description.')) class Meta: app_label = 'rsr' verbose_name = _('organisation recipient country budget') verbose_name_plural = _('organisation recipient country budgets') def iati_country(self): return codelist_value(Country, self, 'country') def iati_country_unicode(self): return str(self.iati_country())
class RecipientCountry(models.Model): project = models.ForeignKey('Project', on_delete=models.CASCADE, verbose_name=_('project'), related_name='recipient_countries') country = ValidXMLCharField( _('recipient country'), blank=True, max_length=2, choices=codelist_choices(COUNTRY, show_code=False), help_text=_('The country that benefits from the project.')) percentage = models.DecimalField( _('recipient country percentage'), blank=True, null=True, max_digits=4, decimal_places=1, validators=[MaxValueValidator(100), MinValueValidator(0)], help_text= _('The percentage of total commitments or total activity budget allocated to ' 'this country. Content must be a positive decimal number between 0 and 100, ' 'with no percentage sign. Percentages for all reported countries and regions ' 'MUST add up to 100%. Use a period to denote decimals.')) text = ValidXMLCharField( _('recipient country description'), blank=True, max_length=50, help_text= _('Enter additional information about the recipient country, if necessary.' )) def __str__(self): if self.country: try: country_unicode = self.iati_country().name except (AttributeError, codelist_models.Country.DoesNotExist): country_unicode = self.country else: country_unicode = '%s' % _('No country specified') if self.percentage: country_unicode += ' (%s%%)' % str(self.percentage) return country_unicode def iati_country(self): return codelist_value(codelist_models.Country, self, 'country') def iati_country_unicode(self): return str(self.iati_country()) class Meta: app_label = 'rsr' verbose_name = _('recipient country') verbose_name_plural = _('recipient countries') ordering = ('-percentage', 'country')
def update_directory(request): """REST view for the update directory.""" # Fetch updates based on whether we are on Akvo site or RSR main site page = request.rsr_page all_updates = _all_updates() if not page else page.updates() # Filter updates based on query parameters filter_, text_filter = _create_filters_query(request) updates = all_updates.filter( filter_).distinct() if filter_ is not None else all_updates updates_text_filtered = updates.filter( text_filter) if text_filter is not None else updates if updates_text_filtered.exists(): updates = updates_text_filtered # Get the relevant data for typeaheads based on filtered updates (minus # text filtering, if no updates were found) locations = [{ 'id': choice[0], 'name': choice[1] } for choice in location_choices(updates)] project_ids = updates.values_list('project__id', flat=True) projects = Project.objects.filter(id__in=project_ids) organisations = projects.all_partners().values('id', 'name', 'long_name') # FIXME: Currently only vocabulary 2 is supported (as was the case with # static filters). This could be extended to other vocabularies, in future. valid_sectors = dict(codelist_choices(SECTOR_CATEGORY)) sectors = projects.sectors().filter( vocabulary='2', sector_code__in=valid_sectors).values('sector_code').distinct() count = updates_text_filtered.count() display_updates = get_qs_elements_for_page(updates_text_filtered, request, count) display_updates = display_updates.select_related( 'project', 'project__primary_location', 'project__primary_organisation', 'user', ).prefetch_related('project__partners', 'project__sectors', 'locations', 'locations__country') response = { 'project_count': count, 'projects': ProjectUpdateDirectorySerializer(display_updates, many=True).data, 'organisation': TypeaheadOrganisationSerializer(organisations, many=True).data, 'location': locations, 'sector': TypeaheadSectorSerializer(sectors, many=True).data, 'page_size_default': settings.PROJECT_DIRECTORY_PAGE_SIZES[0], } return Response(response)
def update_directory(request): """REST view for the update directory.""" # Fetch updates based on whether we are on Akvo site or RSR main site page = request.rsr_page all_updates = _all_updates() if not page else _page_updates(page) # Filter updates based on query parameters filter_, text_filter = _create_filters_query(request) updates = all_updates.filter(filter_).distinct() if filter_ is not None else all_updates updates_text_filtered = updates.filter(text_filter) if text_filter is not None else updates if updates_text_filtered.exists(): updates = updates_text_filtered # Get the relevant data for typeaheads based on filtered updates (minus # text filtering, if no updates were found) locations = [ {'id': choice[0], 'name': choice[1]} for choice in location_choices(updates) ] project_ids = updates.values_list('project__id', flat=True) projects = Project.objects.filter(id__in=project_ids) organisations = projects.all_partners().values('id', 'name', 'long_name') # FIXME: Currently only vocabulary 2 is supported (as was the case with # static filters). This could be extended to other vocabularies, in future. valid_sectors = dict(codelist_choices(SECTOR_CATEGORY)) sectors = projects.sectors().filter( vocabulary='2', sector_code__in=valid_sectors ).values('sector_code').distinct() display_updates = get_qs_elements_for_page(updates_text_filtered, request) count = updates_text_filtered.count() display_updates = display_updates.select_related( 'project', 'project__primary_location', 'project__primary_organisation', 'user', ).prefetch_related( 'project__partners', 'project__sectors', 'locations', 'locations__country' ) response = { 'project_count': count, 'projects': ProjectUpdateDirectorySerializer(display_updates, many=True).data, 'organisation': TypeaheadOrganisationSerializer(organisations, many=True).data, 'location': locations, 'sector': TypeaheadSectorSerializer(sectors, many=True).data, 'page_size_default': settings.PROJECT_DIRECTORY_PAGE_SIZES[0], } return Response(response)
class ProjectContact(models.Model): project = models.ForeignKey('Project', on_delete=models.CASCADE, verbose_name=_('project'), related_name='contacts') type = ValidXMLCharField( _('contact type'), blank=True, max_length=1, choices=codelist_choices(CONTACT_TYPE), help_text=_('What types of enquiries this contact person is best-placed to handle.') ) person_name = ValidXMLCharField( _('contact name'), blank=True, max_length=100, help_text=_('Please enter the name of the contact person for this project.') ) email = models.EmailField(_('contact email'), blank=True) job_title = ValidXMLCharField( _('job title'), max_length=100, blank=True, help_text=_('Job title of the contact person.') ) organisation = ValidXMLCharField( _('contact organisation'), blank=True, max_length=100, help_text=_('The organisation that the contact person works for.') ) telephone = ValidXMLCharField( _('contact phone number'), blank=True, max_length=30, help_text=_('Contact number for the contact person. Avoid giving personal contact ' 'details.') ) mailing_address = ValidXMLCharField( _('contact address'), max_length=255, blank=True, help_text=_('Address of the contact person. Avoid giving personal contact details.') ) state = ValidXMLCharField(_('state'), blank=True, max_length=100, help_text=_('(100 characters)')) country = models.ForeignKey('Country', on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('country'), related_name='contacts') department = ValidXMLCharField(_('department'), blank=True, max_length=100) website = models.URLField( _('contact website'), blank=True, help_text=_('The contact web address, if available. The web address should start with ' '\'http://\' or \'https://\'.') ) def iati_type(self): return codelist_value(ContactType, self, 'type') def iati_type_unicode(self): return str(self.iati_type()) class Meta: app_label = 'rsr' verbose_name = _('contact') verbose_name_plural = _('contacts') ordering = ('id',) def __str__(self): return self.person_name if self.person_name else '%s' % _('No contact name specified')
class IndicatorReference(models.Model): project_relation = 'results__indicators__references__in' indicator = models.ForeignKey(Indicator, on_delete=models.CASCADE, verbose_name=_('indicator'), related_name='references') reference = ValidXMLCharField( _('reference code'), blank=True, max_length=25, help_text= _('A code for an indicator defined in the specified vocabulary specified. ' 'For more information on the indicator reference, see the ' '<a href="http://iatistandard.org/202/activity-standard/iati-activities/' 'iati-activity/result/indicator/reference/" target="_blank">IATI ' 'codelist</a>.')) vocabulary = ValidXMLCharField( _('reference vocabulary'), blank=True, max_length=2, choices=codelist_choices(INDICATOR_VOCABULARY), help_text= _('This is the code for the vocabulary used to describe the sector. Sectors ' 'should be mapped to DAC sectors to enable international comparison. ' 'For more information on the indicator reference, see the ' '<a href="http://iatistandard.org/202/codelists/IndicatorVocabulary/" ' 'target="_blank">IATI codelist</a>.')) vocabulary_uri = ValidXMLCharField( _('reference indicator URI'), blank=True, max_length=1000, help_text=_( 'If the vocabulary is 99 (reporting organisation), the URI where this ' 'internal vocabulary is defined.')) class Meta: app_label = 'rsr' verbose_name = _('indicator reference') verbose_name_plural = _('indicator references') ordering = ('pk', ) def __str__(self): return self.reference def iati_vocabulary(self): return codelist_value(IndicatorVocabulary, self, 'vocabulary') def iati_vocabulary_unicode(self): return str(self.iati_vocabulary())
class FssForecast(models.Model): """ Forecast items for an OECD DAC Forward Spending Survey item. """ fss = models.ForeignKey('Fss', on_delete=models.CASCADE, verbose_name=_('fss'), related_name='forecasts') year = models.PositiveIntegerField( _('year'), blank=True, null=True, help_text=_('The calendar year that the forward spend covers.')) value_date = models.DateField( _('value date'), blank=True, null=True, help_text=_( 'Enter the specific date (DD/MM/YYYY) for the forecast value.')) currency = ValidXMLCharField(_('currency'), blank=True, max_length=3, choices=codelist_choices(CURRENCY)) value = models.DecimalField( _('forecast value'), max_digits=10, decimal_places=2, blank=True, null=True, help_text=_('The forecast value for each year.')) def __str__(self): if self.value and self.currency: try: return '{0} {1}'.format(self.iati_currency().name, self.value) except AttributeError: return '{0} {1}'.format(self.iati_currency(), self.value) else: return '%s' % _('No currency or interest received specified') def iati_currency(self): return codelist_value(Currency, self, 'currency') def iati_currency_unicode(self): return str(self.iati_currency()) class Meta: app_label = 'rsr' verbose_name = _('FSS forecast') verbose_name_plural = _('FSS forecasts') ordering = ('pk', )
class OrganisationLocation(BaseLocation): location_target = models.ForeignKey('Organisation', related_name='locations') iati_country = ValidXMLCharField( _(u'country'), blank=True, max_length=2, choices=codelist_choices(COUNTRY, show_code=False), help_text=_(u'The country in which the organisation is located.')) def iati_country_value(self): return codelist_value(Country, self, 'iati_country') def iati_country_value_unicode(self): return unicode(self.iati_country_value())
class LineBasic(models.Model): currency = ValidXMLCharField(_('currency'), max_length=3, blank=True, choices=codelist_choices(CURRENCY)) value = models.DecimalField( _('value'), max_digits=20, decimal_places=2, null=True, blank=True, help_text= _('Enter the amount of this specific line. Use a period to denote decimals.' )) value_date = models.DateField( _('value date'), null=True, blank=True, help_text=_( 'Enter the date (DD/MM/YYYY) to be used for determining the exchange rate for ' 'currency conversions.')) reference = ValidXMLCharField( _('reference'), blank=True, max_length=50, help_text=_( 'An internal reference that describes the line in the reporting ' 'organisation\'s own system')) text = ValidXMLCharField(_('description'), blank=True, max_length=1000, help_text=_('The description for this line.')) class Meta: app_label = 'rsr' abstract = True def __str__(self): if self.value and self.currency: return '%s %s' % (self.currency, '{:,}'.format(int(self.value))) else: return '%s' % _('No currency or value specified') def iati_currency(self): return codelist_value(Currency, self, 'currency') def iati_currency_unicode(self): return str(self.iati_currency())
class CountryBudgetItem(models.Model): project = models.ForeignKey('Project', on_delete=models.CASCADE, verbose_name=_('project'), related_name='country_budget_items') code = ValidXMLCharField( _('country budget item'), max_length=10, blank=True, choices=codelist_choices(BUDGET_IDENTIFIER), help_text= _('This item encodes the alignment of activities with both the functional and ' 'administrative classifications used in the recipient country’s Chart of ' 'Accounts. This applies to both on- and off-budget activities.')) description = ValidXMLCharField( _('country budget item description'), max_length=100, blank=True, ) percentage = models.DecimalField( _('country budget item percentage'), blank=True, null=True, max_digits=4, decimal_places=1, validators=[MaxValueValidator(100), MinValueValidator(0)], help_text= _('If more than one identifier is reported, the percentage share must be ' 'reported and all percentages should add up to 100 percent. Use a period to ' 'denote decimals.')) def __str__(self): return self.iati_code( ).name if self.code else '%s' % _('No code specified') def iati_code(self): return codelist_value(BudgetIdentifier, self, 'code') def iati_code_unicode(self): return str(self.iati_code()) class Meta: app_label = 'rsr' verbose_name = _('country budget item') verbose_name_plural = _('country budget items') ordering = ('pk', )
class TypeaheadSectorSerializer(serializers.ModelSerializer): id = serializers.SerializerMethodField() name = serializers.SerializerMethodField() # Lookup attribute for sector names. Not a serialized attribute. sectors = dict(codelist_choices(SECTOR_CATEGORY, show_code=False)) def get_id(self, obj): return obj['sector_code'] def get_name(self, obj): return self.sectors[obj['sector_code']] class Meta: model = Sector fields = ('id', 'name')
class ProjectFilter(django_filters.FilterSet): category = django_filters.ChoiceFilter(choices=( [('', _('All'))] + list(Category.objects.all().values_list('id', 'name', flat=False))), label=_(u'category'), name='categories__id') location = django_filters.ChoiceFilter(choices=M49_CODES, label=_(u'location'), action=filter_m49) sector = django_filters.ChoiceFilter(initial=_('All'), choices=([('', _('All'))] + sectors()), label=_(u'sector'), name='sectors__sector_code') status = django_filters.ChoiceFilter(initial=_('All'), label=_(u'status'), choices=ANY_CHOICE + Project.STATUSES) iati_status = django_filters.ChoiceFilter( initial=_('All'), label=_(u'status'), choices=([('', _('All'))] + codelist_choices(ACTIVITY_STATUS, False))) title = django_filters.CharFilter(lookup_type='icontains', label=_(u'Search'), name='title') organisation = django_filters.ChoiceFilter(choices=get_orgs(), label=_(u'organisation'), name='partners__id') class Meta: model = Project fields = [ 'status', 'iati_status', 'location', 'organisation', 'category', 'sector', 'title', ]
class Result(models.Model): project = models.ForeignKey('Project', verbose_name=_(u'project'), related_name='results') title = ValidXMLCharField( _(u'title'), blank=True, max_length=255, help_text=_( u'Enter the title of the result for this project. (255 characters)' )) type = ValidXMLCharField( _(u'type'), blank=True, max_length=1, choices=codelist_choices(RESULT_TYPE), help_text=_( u'Select whether the result is an output, outcome or impact. ' u'<a href="http://www.tacticalphilanthropy.com/2010/06/' u'outputs-outcomes-impact-oh-my/" target="_blank">' u'Further explanation on result types</a>')) aggregation_status = models.NullBooleanField(_(u'aggregation status'), blank=True) description = ValidXMLCharField( _(u'description'), blank=True, max_length=2000, help_text= _(u'You can provide further information of the result here. (2000 characters)' )) def __unicode__(self): return self.title def iati_type(self): return codelist_value(ResultType, self, 'type') def has_info(self): if self.title or self.type or self.aggregation_status or self.description: return True return False class Meta: app_label = 'rsr' verbose_name = _(u'result') verbose_name_plural = _(u'results')
class AdministrativeLocation(models.Model): project_relation = 'locations__administratives__in' location = models.ForeignKey('ProjectLocation', on_delete=models.CASCADE, verbose_name=_('location'), related_name='administratives') code = ValidXMLCharField( _('administrative code'), blank=True, max_length=25, help_text= _('Coded identification of national and sub-national divisions according to ' 'recognised administrative boundary repositories. Multiple levels may be ' 'reported.')) vocabulary = ValidXMLCharField( _('administrative vocabulary'), blank=True, max_length=2, choices=codelist_choices(GEOGRAPHIC_VOCABULARY), help_text=_( 'For reference: <a href="http://iatistandard.org/202/codelists/' 'GeographicVocabulary/" target="_blank">http://iatistandard.org/202/codelists/' 'GeographicVocabulary/</a>.')) level = models.PositiveSmallIntegerField(_('administrative level'), blank=True, null=True) def __str__(self): return str(self.code) if self.code else '%s' % _('No code specified') def iati_vocabulary(self): return codelist_value(GeographicVocabulary, self, 'vocabulary') def iati_vocabulary_unicode(self): return str(self.iati_vocabulary()) class Meta: app_label = 'rsr' verbose_name = _('location administrative') verbose_name_plural = _('location administratives') ordering = ('pk', )
class OrganisationBudget(OrganisationFinanceBasic): status = ValidXMLCharField( _('status'), max_length=1, blank=True, choices=codelist_choices(BUDGET_STATUS), help_text=_( 'The status explains whether the budget being reported is indicative or has ' 'been formally committed.')) class Meta: app_label = 'rsr' abstract = True def iati_status(self): return codelist_value(BudgetStatus, self, 'status') def iati_status_unicode(self): return str(self.iati_status())
class CrsAddOtherFlag(models.Model): """ Other flag of CRS++ reporting. """ crs = models.ForeignKey('CrsAdd', verbose_name=u'crs', related_name='other_flags') code = ValidXMLCharField(_(u'code'), max_length=1, choices=codelist_choices(C_R_S_ADD_OTHER_FLAGS)) significance = models.NullBooleanField(_(u'significance'), blank=True) def iati_code(self): return codelist_value(CRSAddOtherFlags, self, 'code') class Meta: app_label = 'rsr' verbose_name = _(u'CRS other flag') verbose_name_plural = _(u'CRS other flags')
class ProjectCondition(models.Model): project = models.ForeignKey('Project', on_delete=models.CASCADE, verbose_name=_('project'), related_name='conditions') text = ValidXMLCharField( _('condition'), blank=True, max_length=100, help_text= _('The text of a specific condition attached to the Project. Organisation-wide ' 'terms and conditions that apply to all activities should not be reported ' 'here, but in either iati-organisation/document-link or ' 'iati-activity-document-link.')) type = ValidXMLCharField( _('condition type'), blank=True, max_length=1, choices=codelist_choices(CONDITION_TYPE), help_text= _('Condition type – e.g. policy, performance.<br/>' '1 - Policy: The condition attached requires a particular policy to be ' 'implemented by the recipient<br/>' '2 - Performance: The condition attached requires certain outputs or outcomes ' 'to be achieved by the recipient<br/>' '3 - Fiduciary: The condition attached requires use of certain public ' 'financial management or public accountability measures by the recipient' )) def __str__(self): return self.text if self.text else '%s' % _('No condition specified') def iati_type(self): return codelist_value(ConditionType, self, 'type') def iati_type_unicode(self): return str(self.iati_type()) class Meta: app_label = 'rsr' verbose_name = _('condition') verbose_name_plural = _('conditions') ordering = ('pk', )
class ProjectCondition(models.Model): project = models.ForeignKey('Project', verbose_name=_(u'project'), related_name='conditions') text = ValidXMLCharField(_(u'condition'), blank=True, max_length=100, help_text=_(u'(100 characters)')) type = ValidXMLCharField(_(u'condition type'), blank=True, max_length=1, choices=codelist_choices(CONDITION_TYPE)) def iati_type(self): return codelist_value(ConditionType, self, 'type') class Meta: app_label = 'rsr' verbose_name = _(u'condition') verbose_name_plural = _(u'conditions')
def sectors(): sectors_list = [] for sector in codelist_choices(SECTOR_CATEGORY): sectors_list.append(sector) return sectors_list
def project_directory(request): """Return the values for various project filters. Based on the current filters, it returns new options for all the (other) filters. This is used to generate dynamic filters. """ # Fetch projects based on whether we are an Akvo site or RSR main site page = request.rsr_page projects = page.projects() if page else Project.objects.all().public().published() # Exclude projects which don't have an image or a title # FIXME: This happens silently and may be confusing? projects = projects.exclude(Q(title='') | Q(current_image='')) # Filter projects based on query parameters filter_, text_filter = _create_filters_query(request) projects = projects.filter(filter_).distinct() if filter_ is not None else projects # NOTE: The text filter is handled differently/separately from the other filters. # The text filter allows users to enter free form text, which could result in no # projects being found for the given text. Other fields only allow selecting from # a list of options, and for every combination that is shown to users and # selectable by them, at least one project exists. # When no projects are returned for a given search string, if the text search is # not handled separately, the options for all the other filters are empty, and # this causes the filters to get cleared automatically. This is very weird UX. projects_text_filtered = ( projects.filter(text_filter) if text_filter is not None else projects ) if projects_text_filtered.exists(): projects = projects_text_filtered # Pre-fetch related fields to make things faster projects = projects.select_related( 'primary_location', 'primary_organisation', ).prefetch_related( 'locations', 'locations__country', 'recipient_countries', 'recipient_countries__country', ) # Get the relevant data for typeaheads based on filtered projects (minus # text filtering, if no projects were found) cached_locations, _ = get_cached_data(request, 'locations', None, None) if cached_locations is None: cached_locations = [ {'id': choice[0], 'name': choice[1]} for choice in location_choices(projects) ] set_cached_data(request, 'locations', cached_locations) organisations = projects.all_partners().values('id', 'name', 'long_name') # FIXME: Currently only vocabulary 2 is supported (as was the case with # static filters). This could be extended to other vocabularies, in future. valid_sectors = dict(codelist_choices(SECTOR_CATEGORY)) sectors = projects.sectors().filter( vocabulary='2', sector_code__in=valid_sectors ).values('sector_code').distinct() # NOTE: We use projects_text_filtered for displaying projects count = projects_text_filtered.count() display_projects = get_qs_elements_for_page(projects_text_filtered, request).select_related( 'primary_organisation' ) # NOTE: We use the _get_cached_data function to individually cache small # bits of data to avoid the response from never getting saved in the cache, # because the response is larger than the max size of data that can be # saved in the cache. cached_projects, showing_cached_projects = get_cached_data( request, 'projects', display_projects, ProjectDirectorySerializer ) cached_organisations, _ = get_cached_data( request, 'organisations', organisations, TypeaheadOrganisationSerializer ) response = { 'project_count': count, 'projects': cached_projects, 'showing_cached_projects': showing_cached_projects, 'organisation': cached_organisations, 'sector': TypeaheadSectorSerializer(sectors, many=True).data, 'location': cached_locations, 'page_size_default': settings.PROJECT_DIRECTORY_PAGE_SIZES[0], } return Response(response)