From 02f718629e2fb827623404344ef17c666bd26e7b Mon Sep 17 00:00:00 2001 From: Adrian Collier Date: Thu, 10 Apr 2014 13:30:59 +0200 Subject: [PATCH 01/26] #510 added resource and tidied up docs --- docs/RSR Features/API/API-Resources.md | 44 ++ .../API/Akvo-RSR-API-Content-Requirements.md | 586 ------------------ docs/RSR Features/API/README.md | 7 +- docs/RSR Features/API/RSR-API.md | 8 - 4 files changed, 49 insertions(+), 596 deletions(-) create mode 100644 docs/RSR Features/API/API-Resources.md delete mode 100644 docs/RSR Features/API/Akvo-RSR-API-Content-Requirements.md delete mode 100644 docs/RSR Features/API/RSR-API.md diff --git a/docs/RSR Features/API/API-Resources.md b/docs/RSR Features/API/API-Resources.md new file mode 100644 index 0000000000..2d6e4b6d9a --- /dev/null +++ b/docs/RSR Features/API/API-Resources.md @@ -0,0 +1,44 @@ +## API Resources + +In order to make calls to the API for commonly used features, we have started to create cusotmised resources to provide the exact information that is needed. + +#### Project Update Resource + +We have created a resource to encapsulate all the information required to read and display a Project Update within a website or other visual interface. + +http://rsr.akvo.org/api/v1/project_update_extra/?format=json + +This resource contains the following information: + +``` + "absolute_url": "/project/880/update/5077/", + "id": 5077, + "language": "en", + "notes": "", + "photo": "/media/db/project/880/update/5077/ProjectUpdate_5077_photo_2014-04-10_11.13.26.jpg", + "photo_caption": "", + "photo_credit": "", + "photo_location": "E", + "project": "/api/v1/project/880/", + "resource_uri": "/api/v1/project_update_extra/5077/", + "text": "To increase storage capacity of water sources and accessibility for domestic and multiple uses of water by the community CARE WASH program has constructed 20,,000 cubic metres pan for Antut community. The pan of its kind is improved one with infiltration gallery, well fenced and has VIP latrine. Community appreciated the work. The ongoing construction work is at the final stage of installation of hand pump.\n", + "time": "2014-04-10T11:13:26", + "time_last_updated": "2014-04-10T11:13:26", + "title": "Construction of new water pan - Antut", + "update_method": "M", + "user": { + "full_name": "Dima Bonaya", + "organisation": { + "absolute_url": "/organisation/1194/", + "long_name": "CARE International in Kenya ", + "name": "CARE - Kenya", + "resource_uri": "/api/v1/organisation/1194/" + }, + "resource_uri": "/api/v1/user/1203/" + }, + "user_agent": "Akvo RSR Up v1.0 on Android 2.3.6 device samsung GT-S5830i", + "uuid": "77eefef4-098f-4ccb-966c-db8faf3e9396", + "video": "", + "video_caption": "", + "video_credit": "" +``` diff --git a/docs/RSR Features/API/Akvo-RSR-API-Content-Requirements.md b/docs/RSR Features/API/Akvo-RSR-API-Content-Requirements.md deleted file mode 100644 index 91a526183f..0000000000 --- a/docs/RSR Features/API/Akvo-RSR-API-Content-Requirements.md +++ /dev/null @@ -1,586 +0,0 @@ -We have developed a Write API to be able to create information within Akvo RSR through file submission. -To ensure that the content is suitable to create projects with, the data should conform the to the requirements on this page. - -### [Project Content](https://github.com/akvo/akvo-rsr/wiki/Akvo-RSR-API-Content-Requirements#project-content-1) -### [Goals](https://github.com/akvo/akvo-rsr/wiki/Akvo-RSR-API-Content-Requirements#goals-1) -### [Locations](https://github.com/akvo/akvo-rsr/wiki/Akvo-RSR-API-Content-Requirements#locations-1) -### [Budgets](https://github.com/akvo/akvo-rsr/wiki/Akvo-RSR-API-Content-Requirements#budgets-1) -### [Organisations](https://github.com/akvo/akvo-rsr/wiki/Akvo-RSR-API-Content-Requirements#organisations-1) -### [Images](https://github.com/akvo/akvo-rsr/wiki/Akvo-RSR-API-Content-Requirements#images-1) -### [Indicators, Categories & Focus Areas](https://github.com/akvo/akvo-rsr/wiki/Akvo-RSR-API-Content-Requirements#indicators-categories--focus-areas-1) - -Project Content -=============== - -All content that resides on the rsr_project database table is referred to as Core Project Content. - -Core Content Requirements: -* Title (45)* -* Subtitle (75)* -* Status* - - Active - - Needs Funding - - Complete - - Archived (for existing projects only) -* Project Language* - - English - - Dutch - - French - - German - - Spanish - - Russian -* Project Start Date (yyyy-mm-dd)* -- Project End Date (yyyy-mm-dd) -* Project Plan Summary (400)* -- Background (1000) -- Current Status (600) -- Project Plan (∞) -* Sustainability (∞)* -* Overview of Goals (600)* -- Photo Caption (50) -- Currency (€/$) -- Admin Notes (∞) - - -Goals -===== - -Individual Goals are added to a project. -We can accept up to 8 Goals per project. - -Goals Requirements: -* Description (100)* - - -Locations -========= - -A project must have at least one Location included, but may have many more. -The details included in the Location will provide the information to populate maps. -One Location may be marked as the Primary Location. -If no Location is marked as primary, the first Location will be used as the Primary Location. - -Location Requirements: -* Latitude* -* Longitude* -- City (255) -- State (255) -* Country (ISO 3166)* -- Address 1 (255) -- Address 2 (255) -- Postcode (10) -- Primary Location (Y/N - 1 permitted per project) - - -Budgets -======= - -Project budget information is used to show where funds are to be spent. -Budget information is split into individual items categorised by type. -There are 10 types available, with 3 custom labels available per project. -A project can include as many budget items as needed. - -Budget Item Requirements: -* Label* - - Building Materials - - Employment - - Equipment - - Maintenance - - Overhead - - PR & Marketing - - Total - - Training - - Transportation - - Other 1 - - Other 2 - - Other 3 -- If Other 1/2/3: - - Extra Label (20) -* Amount (€/$)* - - -Organisations -============= - -Partnerships ------------- - -When an Organisation is working on a project we refer to this relationship as a Partnership. -Partnerships need to be included within the Project Data to identify the Organisations that are working on the project. -Unless specifically stated otherwise, the Organisation submitting the API Data will be assigned as the Support Partner for the project. - -All additional Organisations working on the project need to be uniquely identified. -We recommend that the IATI Organisation Identifier is used. - -If the IATI Org Id is unknown for one of more Organisations you work with, all Organisations from all projects being submitted should be collated into a separate import file. - -Partnership Requirements: -* Organisation Identifier - Internal or IATI (255)* - If IATI: - * Organisation Type* - - Government - - Other Public Sector - - International NGO - - National NGO - - Regional NGO - - Public Private Partnership - - Multilateral - - Foundation - - Private Sector - - Academic, Training & Research -* Partner Role* - - Field Partner - - Funding Partner - - Sponsor Partner - - Support Partner (1 permitted per project) -- If Field Partner: - - Additional Field Partner Type - - Alliance - - Knowledge - - Network -- If Funding Partner: - - Funding Amount (€/$) -- Partner's Internal Project Identifier (255) - - -Organisation Input File --------------- - -Each Organisation needs to be given a unique reference that will be used consistently throughout this and all future import files. -For subsequent API Imports, only additional Organisations need to be submitted, but a full Organisation list will be accepted. - -Organisation Requirements: -* Name (25)* -- Long Name (75) -* Organisation Type* - - Government - - Other Public Sector - - International NGO - - National NGO - - Regional NGO - - Public Private Partnership - - Multilateral - - Foundation - - Private Sector - - Academic, Training & Research -- Logo (see Image requirements) -- URL (255) -- IATI Organisation ID (255) -- Contact Person (30) -- Contact Email (50) -- Phone (20) -- Mobile (20) -- Fax (20) -- Description (∞) - - -Images -====== - -Each project and organisation should be given an image. -The image should be in 4:3 aspect ratio and no more than 2MB in size. -The image should be transmitted to Akvo with API files or should be hosted in a publicly accessible location. -Each image should be given a filename that uniquely identifies the project or organisation that the image should be attached to. - - -Indicators, Categories & Focus Areas -==================================== - -SOON TO BE DELIVERED PROCESS ----------------------------- -_[Click here for the current process](https://github.com/akvo/akvo-rsr/wiki/_preview#current-process)_ - -Focus Areas ------------ - -Each project must be assigned to one or more Focus Areas. - -Focus Area Requirements: -* Focus Area* - - Economic Development - - Education - - Healthcare - - IT and Communication - - Water and Sanitation - -Indicator and Categories ------------------------- -Indicators have been given a simple but defined structure to enable data management and analysis. -Each Indicator must be given one from each of a set of attributes. -An Indicator may also be given a custom label to be used for public websites when further refinement is needed. -Each Indicator must be given its own category from the IATI Sector list (http://iatistandard.org/codelists/sector/). - -Indicator Requirements: -* Indicator Amount (int)* -- Custom Label (80) -* Indicator Effect* - - Affected - - Created - - Empowered - - Improved - - Organised - - Reached - - Trained -* Indicator Subject* - - Adults - - Children - - Men - - People - - Staff - - Students - - Teachers - - Users - - Women - - Communities - - Entrepreneurs - - Health clinics - - Households - - Institutions - - NGO’s - - Organisations - - Schools - - Events - - Hygiene facilities - - Sanitation facilities - - Sites - - Surveys/research - - Technology facilities - - Training facilities - - Water facilities - - Health facilities - - Resources -* Category (IATI Sector List - selection indicated below)* - - IATI Sector/Category Code (int) - -Subset of IATI Sector/Category List ------------------------------------ - -- 31165 - Agricultural alternative development -- 31120 - Agricultural development -- 31181 - Agricultural education/training -- 14030 - Basic drinking water supply and sanitation -- 12220 - Basic health care/Nursery -- 11230 - Basic life skills -- 25010 - Business support services and institutions -- 15220 - Civilian peace-building -- 22010 - Communcations policy and administrative management -- 15110 - Economic and development policy/planning -- 24081 - Education and training in banking/financial management -- 11110 - Education policy and administrative management -- 11120 - Educational training -- 12261 - Health education -- 12230 - Health infrastructure -- 12281 - Health personal development -- 12110 - Health policy and administrative management -- 13040 - HIV/Aids -- 15162 - Human rights -- 22040 - ICT -- 12191 - Medical services -- 24040 - Micro-credit -- 15261 - Prevention and demobilization of child soldiers -- 22030 - Radio and/or television -- 43040 - Rural development -- 41040 - Site preservation -- 32130 - Small and medium sized enterprises development -- 16010 - Social/welfare services -- 15150 - Strengthening civil society -- 11130 - Teacher training -- 14081 - Training in water supply and sanitation -- 11330 - Vocational education -- 14010 - Water resources and administrative management -- 14020 - Water supply and sanitation (large systems) -- 111 - Education -- 112 - Basic education -- 113 - Secondary education -- 121 - Health -- 122 - Basic health -- 130 - Population policies and reproductive health -- 140 - Water and Sanitation -- 151 - Government and civil society -- 152 - Conflict prevention and resolution, peace and security -- 160 - Social infrastructure and services -- 220 - Communication -- 240 - Banking and financial services -- 250 - Business and other services -- 311 - Agriculture -- 321 - Industry -- 410 - Environmental protection -- 430 - Unspecified sector -- 720 - Emergency response - - -CURRENT PROCESS ---------------- - -Indicators (aka Benchmarks), Categories and Focus Areas are co-dependently linked. -Each Focus Area has a set of Categories. -Each Category has a set of Indicators. -Indicators are only selectable if the project has the parent Category. - -Focus Area and Category Requirements: -* Focus Area* - - Economic Development - - Education - - Healthcare - - IT and Communication - - Water and Sanitation -- If Economic Development: -* Category* - - Small Business Development - - Entrepreneurship trainees - - Social Enterprise Development - - Agriculture - - Economic development - - Heritage Management - - Disaster Recovery - - Entrepreneurship - - Investments - - Security & Justice - - Urban Matters - - Women's Leadership -- If Education: - - Training - - Education - - Archaeological Research - - Education and Outreach - - Vocational Training -- If Healthcare: - - Raising awareness - - HIV / AIDS - - Telemedicine - - Medical training - - Webbased tools - - HMIS - - eHealth - - Community Empowerment - - Training - - Equipment - - Health - - Eye care - - Lobby and Advocacy - - Food Security - - Healthcare -- If IT and Communication: - - Economic development - - ICT - - Training - - Telemedicine - - Mobiles for Development -- If Water and Sanitation - - Water - - Sanitation - - Maintenance - - Training - - Education - - Product development - - Other - - Hygiene - - Capacity Building - - Networking - - Lobby and Advocacy - -Indicators/Benchmarks Requirements: -* Value (ind)* -- If Agriculture: - - farmers groups reached - - farmers trained - - trainees -- If Archaeological Research: - - communities helped - - excavations - - publications - - research programs - - students and/or staff trained -- If Capacity Building: - - NGOs receive capacity assessments - - NGOs receive tailor made trainings - - NGOs trained on Sustainability Assessment - - people trained on budget tracking methodologies - - staff members trained on WASH financing - - staff trained on water resource protection -- If Community Empowerment : - - community educators trained - - people reached -- If Economic development: - - end users - - farmers groups reached - - households reached - - households receive funding - - houses built - - institutions trained - - institutions with improved facilities - - people affected - - people empowered - - people reached - - people reached via radio broadcasting - - people receive funding - - people trained - - people with access to improved facilities - - people with access to information - - producer organisations reached - - producer organizations access to market info - - producers/entrepreneurs increased their income - - producers/entrepreneurs trained - - users - - women empowered - - women groups reached - - women join co-operative - - women lift themselves out of poverty - - women reached - - women receive funding - - years duration - - youth groups reached -- If Education: - - children participating in mixed sports - - children participating in reading aloud - - children reached - - former child soldiers are lifted out of poverty - - former child soldiers complete their education - - new construction - - others in community mentored - - outreach counsellors trained in counseling skills - - people trained - - percentage of girls in secondary education - - School management Committees trained - - schools reached - - students reached - - students trained - - students use ICT - - surveys carried out with beneficiaries - - teachers reached - - teachers trained - - teachers use ICT - - teaching notes uploaded - - trainees - - trauma victims are offered support and counseling - - women learn to read and write - - women learn vocational skills -- If Education and Outreach: - - communities helped - - exhibitions created - - sites interpreted - - teaching programs -- If eHealth: - - e-courses -- If Entrepreneurship trainees: - - entrepreneurship trainees -- If Equipment: - - equipment installed -- If Eye care: - - cataract operations - - euros in medical equipment - - postgraduate seminar trainees -- If Health: - - children reached - - health facilties reached - - health workers trained - - households have access to healthcare - - people affected - - people in communities reached - - people reached - - people with access to health care - - pregnant women reached - - trauma victims are offered support and counseling -- If Heritage Management: - - communities helped - - sites managed and conserved - - students and/or staff trained -- If HIV / AIDS: - - children / (childheaded) families taken care of - - HIV/AIDS tested - - increased awareness - - medical staff receive HIV training - - people reached by HIV socialization -- If HMIS: - - local health clinics - - trainees -- If Hygiene: - - people reached with hygiene related communication - - Persons who receive training/capacity building -- If ICT: - - advocacy events organised - - communities reached - - computer software and hardware - - information systems - - internet connections - - mobile devices - - organisations trained - - people affected - - people impacted by ICT-improved org. services - - staff trained - - students and/or staff trained - - students trained - - trainees - - users - - years duration -- If Lobby and Advocacy: - - areas where budget tracking is on meeting agenda - - NGOs using integrated Sustainability Instrument - - of initiatives to influence water resource policy - - organisations focus on human rights - - organisations strengthened -- If Medical training: - - trainees -- If Mobiles for Development: - - daily SMS on market info. - - people reached - - SMS send monthly -- If Networking: - - active WASH coordination structures - - district level WASH programs implemented - - marginalized groups active in WASH community group - - women active in WASH community group -- If Raising awareness: - - health training materials - - increased awareness - - people reached - - trainees - - with access to health information -- If Sanitation: - - households reached - - hygiene facilities - - institutions with improved sanitation facilities - - people affected - - people use improved sanitation facilities - - sanitation systems - - years duration -- If Small Business Development: - - business centers - - micro-finance bodies - - new clients/customers - - new jobs - - people running sustainable business - - radio station - - trainees -- If Social Enterprise Development: - - new partnerships -- If Telemedicine: - - consultations - - health centers using telemedicine - - hospitals using telemedicine - - medical cases uploaded - - people affected - - staff trained - - users - - years duration -- If Training: - - new jobs - - trainees -- If Vocational Training: - - schools reached - - students reached - - teachers reached - - teachers trained - - women learn vocational skills -- If Water: - - communities reached - - households reached - - people affected - - people in communities reached - - people receive training - - people use improved drinking water - - water systems - - years duration -- If Webbased tools: - - hospitals - - local health clinics \ No newline at end of file diff --git a/docs/RSR Features/API/README.md b/docs/RSR Features/API/README.md index db048c7e7b..7e75fecc49 100644 --- a/docs/RSR Features/API/README.md +++ b/docs/RSR Features/API/README.md @@ -4,17 +4,20 @@ In addition to the manual interaction methods in RSR, we have built a data API t Firstly, we offer the ability to READ all of your data directly. The API provides information in JSON or XML format that can be parsed and implemented directly into a Website or data store. This allows your data to be displayed in any custom format you wish. Additionally you can choose to display, all or part of the available information with no impact on the data entry or maintenance processes. -We are already working with this solution for some time. You can find a list of our Live Partners using this solution [here](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/API-Read-Partners.md) +We are already working with this solution for some time. You can find a list of our Live Partners using this solution here: [API Partners](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/API-Read-Partners.md). If you are interested in using the READ API for displaying your RSR Projects in your Site or Page, please check out our [Technical API Documentation](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/Akvo-RSR-API-developer-documentation.md). +In addition to the API Technical Documentation, we're also working on expanding the list of customised resources created with specific data sets to be visualised. This work is captured within our [API Resources documentation](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/API-Resources.md). + + ### Creating Data using the RSR API Secondly we have created the ability for Partners to be able to export their project information directly from their Internal Project Management System and import this directly into RSR using the API. This product is still being improved and fine-tuned, but it is Live and is available to use. -The file specification and requirements are documented and can be found [here](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/Sample-XML-Files-for-API-Load.md). +The file specification and requirements are documented and can be found [in this folder](https://github.com/akvo/akvo-rsr/tree/wiki-docs/docs/RSR%20Features/API/downloads). We have also documented some of the specific Partner customisations for our implementations within these pages: diff --git a/docs/RSR Features/API/RSR-API.md b/docs/RSR Features/API/RSR-API.md deleted file mode 100644 index e63956673a..0000000000 --- a/docs/RSR Features/API/RSR-API.md +++ /dev/null @@ -1,8 +0,0 @@ -### API Documentation -* [[Akvo RSR API]] -* [[Akvo-RSR-API-developer-documentation]] -* [[Sample XML Files for API Load]] -* [[API Read Partners]] -* [[API Write - Cordaid Implementation]] -* [[API Write RAIN Implementation]] -* [[Generated Akvo RSR API Content Requirements]] \ No newline at end of file From ffb6cdcdb241d2d455774d183d3fbfc17e7a1c6c Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Thu, 1 May 2014 16:28:42 +0200 Subject: [PATCH 02/26] [#252] Removed all xml:lang attributes except default from XML --- akvo/scripts/cordaid/cordaid_project_upload.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/akvo/scripts/cordaid/cordaid_project_upload.py b/akvo/scripts/cordaid/cordaid_project_upload.py index 11a6e33715..3cbd1d569c 100644 --- a/akvo/scripts/cordaid/cordaid_project_upload.py +++ b/akvo/scripts/cordaid/cordaid_project_upload.py @@ -28,6 +28,13 @@ class HttpNoContent(HttpResponse): def post_an_activity(activity_element, user): try: iati_id = activity_element.findall('iati-identifier')[0].text + + # Remove all xml:lang attributes except the one in the iati-activity tag. + for element in activity_element.iter(): + if (not element.tag == 'iati-activity') and\ + ('{http://www.w3.org/XML/1998/namespace}lang' in element.attrib.keys()): + del element.attrib['{http://www.w3.org/XML/1998/namespace}lang'] + project = Requester( method='post', url_template="http://{domain}/api/{api_version}/iati_activity/" @@ -70,6 +77,13 @@ def put_an_activity(activity_element, pk, url_args): url_args.update(pk=pk) try: iati_id = activity_element.findall('iati-identifier')[0].text + + # Remove all xml:lang attributes except the one in the iati-activity tag. + for element in activity_element.iter(): + if (not element.tag == 'iati-activity') and\ + ('{http://www.w3.org/XML/1998/namespace}lang' in element.attrib.keys()): + del element.attrib['{http://www.w3.org/XML/1998/namespace}lang'] + project = Requester( method='put', url_template="http://{domain}/api/{api_version}/iati_activity/{pk}/?" From bddaa554437e35d45428f56ac54ece9e0488e35f Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Fri, 16 May 2014 10:46:49 +0200 Subject: [PATCH 03/26] [#252] Updated language tag check --- .../scripts/cordaid/cordaid_project_upload.py | 91 +++++++++++++++++-- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/akvo/scripts/cordaid/cordaid_project_upload.py b/akvo/scripts/cordaid/cordaid_project_upload.py index 3cbd1d569c..dfb99cb5b5 100644 --- a/akvo/scripts/cordaid/cordaid_project_upload.py +++ b/akvo/scripts/cordaid/cordaid_project_upload.py @@ -7,6 +7,8 @@ import getopt import sys +import collections +import itertools from lxml import etree @@ -25,15 +27,88 @@ class HttpNoContent(HttpResponse): status_code = 204 +def check_activity_language(activity_element): + """Checks whether the activity element has an xml:lang tag. + If so, look for identical elements (with identical attributes) and check if there is an element containing the + xml:lang of the activity element or one element without a xml:lang tag. In these cases, all other elements are + removed.""" + + def compare_dicts(dict1, dict2): + """Compares two dicts while ignoring xml:lang attribute. + Returns True if they're the same and False otherwise.""" + + dict1_extra = 0 + dict2_extra = 0 + + # Check if xml:lang attribute is present in one dict and missing in the other + if '{http://www.w3.org/XML/1998/namespace}lang' in dict1 and \ + (not '{http://www.w3.org/XML/1998/namespace}lang' in dict2): + dict1_extra += 1 + elif '{http://www.w3.org/XML/1998/namespace}lang' in dict2 and \ + (not '{http://www.w3.org/XML/1998/namespace}lang' in dict1): + dict2_extra += 1 + + # Return False if the number of shared attributes is different + shared_keys = set(dict1.keys()) & set(dict2.keys()) + if not ( len(shared_keys) == len(dict1.keys()) - dict1_extra and + len(shared_keys) == len(dict2.keys()) - dict2_extra): + return False + + # Return True if all attributes are similar + dicts_are_equal = True + for key in dict1.keys(): + if key != '{http://www.w3.org/XML/1998/namespace}lang': + dicts_are_equal = dicts_are_equal and (dict1[key] == dict2[key]) + + return dicts_are_equal + + def check_lang(element, lang): + """Check if the element has the xml:lang corresponding to the activity language or no xml:lang attribute. + Return True if so, False otherwise.""" + + if not '{http://www.w3.org/XML/1998/namespace}lang' in element.attrib: + return True + + elif element.attrib['{http://www.w3.org/XML/1998/namespace}lang'].lower() == lang: + return True + + return False + + + if '{http://www.w3.org/XML/1998/namespace}lang' in activity_element.attrib: + lang = activity_element.attrib['{http://www.w3.org/XML/1998/namespace}lang'].lower() + + # For each element in the activity + for element in activity_element.iter(): + + # Look up the elements' children and count their number of appearances + child_tag_list = [child.tag for child in list(element.iterchildren())] + child_tag_list_counter = collections.Counter(child_tag_list) + multiple_children_list = [i for i in child_tag_list_counter if child_tag_list_counter[i]>1] + + # For all children that appear multiple times + for child_tag in multiple_children_list: + children = element.findall(child_tag) + + # Make a comparison for all combinations + for child1, child2 in itertools.combinations(children, 2): + if compare_dicts(child1.attrib, child2.attrib): + # Remove element if xml:lang differs from activity language and there is another element + # that does match the xml:lang or does not have a xml:lang specified. + if check_lang(child1, lang) and not check_lang(child2, lang): + activity_element.remove(child2) + elif not check_lang(child1, lang) and check_lang(child2, lang): + activity_element.remove(child1) + + return activity_element + + + def post_an_activity(activity_element, user): try: iati_id = activity_element.findall('iati-identifier')[0].text - # Remove all xml:lang attributes except the one in the iati-activity tag. - for element in activity_element.iter(): - if (not element.tag == 'iati-activity') and\ - ('{http://www.w3.org/XML/1998/namespace}lang' in element.attrib.keys()): - del element.attrib['{http://www.w3.org/XML/1998/namespace}lang'] + activity_element = check_activity_language(activity_element) project = Requester( method='post', @@ -78,11 +153,7 @@ def put_an_activity(activity_element, pk, url_args): try: iati_id = activity_element.findall('iati-identifier')[0].text - # Remove all xml:lang attributes except the one in the iati-activity tag. - for element in activity_element.iter(): - if (not element.tag == 'iati-activity') and\ - ('{http://www.w3.org/XML/1998/namespace}lang' in element.attrib.keys()): - del element.attrib['{http://www.w3.org/XML/1998/namespace}lang'] + activity_element = check_activity_language(activity_element) project = Requester( method='put', From 99bea7fc65192b6a445c5b9d5fae3aba27469533 Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Thu, 22 May 2014 12:14:48 +0200 Subject: [PATCH 04/26] [#252] Remove correct child nodes --- .../scripts/cordaid/cordaid_project_upload.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/akvo/scripts/cordaid/cordaid_project_upload.py b/akvo/scripts/cordaid/cordaid_project_upload.py index dfb99cb5b5..61e8f65a0e 100644 --- a/akvo/scripts/cordaid/cordaid_project_upload.py +++ b/akvo/scripts/cordaid/cordaid_project_upload.py @@ -23,6 +23,7 @@ from akvo.scripts.cordaid import log, API_VERSION, CORDAID_IATI_ACTIVITIES_XML, CORDAID_UPLOAD_CSV_FILE, ACTION_CREATE_PROJECT, ERROR_EXCEPTION, ERROR_UPLOAD_ACTIVITY, ERROR_CREATE_ACTIVITY, ERROR_UPDATE_ACTIVITY, ACTION_UPDATE_PROJECT, CORDAID_ACTIVITIES_CSV_FILE, print_log, init_log, ERROR_MULTIPLE_OBJECTS, ERROR_NO_ORGS from requester import Requester +XML_LANG = "{http://www.w3.org/XML/1998/namespace}lang" class HttpNoContent(HttpResponse): status_code = 204 @@ -41,23 +42,21 @@ def compare_dicts(dict1, dict2): dict2_extra = 0 # Check if xml:lang attribute is present in one dict and missing in the other - if '{http://www.w3.org/XML/1998/namespace}lang' in dict1 and \ - (not '{http://www.w3.org/XML/1998/namespace}lang' in dict2): + if XML_LANG in dict1 and (not XML_LANG in dict2): dict1_extra += 1 - elif '{http://www.w3.org/XML/1998/namespace}lang' in dict2 and \ - (not '{http://www.w3.org/XML/1998/namespace}lang' in dict1): + elif XML_LANG in dict2 and (not XML_LANG in dict1): dict2_extra += 1 # Return False if the number of shared attributes is different shared_keys = set(dict1.keys()) & set(dict2.keys()) - if not ( len(shared_keys) == len(dict1.keys()) - dict1_extra and + if not (len(shared_keys) == len(dict1.keys()) - dict1_extra and len(shared_keys) == len(dict2.keys()) - dict2_extra): return False # Return True if all attributes are similar dicts_are_equal = True for key in dict1.keys(): - if key != '{http://www.w3.org/XML/1998/namespace}lang': + if key != XML_LANG: dicts_are_equal = dicts_are_equal and (dict1[key] == dict2[key]) return dicts_are_equal @@ -66,17 +65,17 @@ def check_lang(element, lang): """Check if the element has the xml:lang corresponding to the activity language or no xml:lang attribute. Return True if so, False otherwise.""" - if not '{http://www.w3.org/XML/1998/namespace}lang' in element.attrib: + if not XML_LANG in element.attrib: return True - elif element.attrib['{http://www.w3.org/XML/1998/namespace}lang'].lower() == lang: + elif element.attrib[XML_LANG].lower() == lang: return True return False - if '{http://www.w3.org/XML/1998/namespace}lang' in activity_element.attrib: - lang = activity_element.attrib['{http://www.w3.org/XML/1998/namespace}lang'].lower() + if XML_LANG in activity_element.attrib: + lang = activity_element.attrib[XML_LANG].lower() # For each element in the activity for element in activity_element.iter(): @@ -96,9 +95,9 @@ def check_lang(element, lang): # Remove element if xml:lang differs from activity language and there is another element # that does match the xml:lang or does not have a xml:lang specified. if check_lang(child1, lang) and not check_lang(child2, lang): - activity_element.remove(child2) + child2.getparent().remove(child2) elif not check_lang(child1, lang) and check_lang(child2, lang): - activity_element.remove(child1) + child1.getparent().remove(child1) return activity_element From eac9e2b3aab4963e2785679807eec3a1e28410a7 Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Thu, 22 May 2014 12:18:34 +0200 Subject: [PATCH 05/26] [#252] Removed unnecessary child.getparent() call --- akvo/scripts/cordaid/cordaid_project_upload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akvo/scripts/cordaid/cordaid_project_upload.py b/akvo/scripts/cordaid/cordaid_project_upload.py index 61e8f65a0e..9f714796e1 100644 --- a/akvo/scripts/cordaid/cordaid_project_upload.py +++ b/akvo/scripts/cordaid/cordaid_project_upload.py @@ -95,9 +95,9 @@ def check_lang(element, lang): # Remove element if xml:lang differs from activity language and there is another element # that does match the xml:lang or does not have a xml:lang specified. if check_lang(child1, lang) and not check_lang(child2, lang): - child2.getparent().remove(child2) + element.remove(child2) elif not check_lang(child1, lang) and check_lang(child2, lang): - child1.getparent().remove(child1) + element.remove(child1) return activity_element From 746a77293a5ca37e177cafc6376e6c45d5500178 Mon Sep 17 00:00:00 2001 From: Adrian Collier Date: Mon, 16 Jun 2014 12:23:23 +0200 Subject: [PATCH 06/26] #510 addition of remaining custom resources --- docs/RSR Features/API/API-Resources.md | 166 +++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/docs/RSR Features/API/API-Resources.md b/docs/RSR Features/API/API-Resources.md index 2d6e4b6d9a..3f3762f64c 100644 --- a/docs/RSR Features/API/API-Resources.md +++ b/docs/RSR Features/API/API-Resources.md @@ -42,3 +42,169 @@ This resource contains the following information: "video_caption": "", "video_credit": "" ``` + +#### Map for Project Resource + +We have created a resource that provides all the information about projects that is needed to visualise them on a map including population of an info window that gives extra information about the point selected. This includes the location, title and image information. + +http://rsr.akvo.org/api/v1/map_for_project/?format=json + +This resource contains the following information: + +``` + "absolute_url": "/project/2019/", + "budget": "49258.00", + "created_at": "2014-06-04T16:59:01", + "currency": "EUR", + "current_image": { + + "original": "/media/db/project/2019/Project_2019_current_image_2014-06-04_17.00.13.jpg", + "thumbnails": { + "fb_thumb": "/media/db/project/2019/Project_2019_current_image_2014-06-04_17.00.13_jpg_200x200_pad_q85.jpg", + "map_thumb": "/media/db/project/2019/Project_2019_current_image_2014-06-04_17.00.13_jpg_160x120_autocrop_detail_q85.jpg" + } + + }, + "current_image_caption": "", + "current_image_credit": "", + "date_complete": "2016-06-04", + "date_request_posted": "2014-06-04", + "donate_button": true, + "funds": "0.00", + "funds_needed": "49258.00", + "id": 2019, + "language": "en", + "last_modified_at": "2014-06-15T09:00:03", + "primary_location": { + + "address_1": "", + "address_2": "", + "city": "Bilibiza", + "country": "/api/v1/country/53/", + "id": 4956, + "latitude": -12.566667, + "longitude": 40.283333, + "postcode": "", + "primary": true, + "resource_uri": "/api/v1/project_map_location/4956/", + "state": "" + + }, + "project_plan_summary": "This project will:\r\n1 Strengthen local partner GSB to become a WASH production & training center\r\n2 Provide 5000 people with water and sanitation via ground water recharge, hand dug or drilled wells, Rope pumps, household water filters, improved latrines etc\r\n3 Focus water activities on Self-supply for income generating via small scale irrigation by training of smallholder farmers. ", + "resource_uri": "/api/v1/map_for_project/2019/", + "status": "H", + "subtitle": "Production & Training centre for Low cost WASH Solutions", + "target_group": "The rural population who belong to the poorest people in Mozambique. Only 22.6% of the households reported that they produce enough food. Between 16% and 42% of the population have access to water from protected wells. Very few people have a latrine.\r\n\r\nVillages will be selected on occurrence of water borne diseases like cholera, willingness to start a Farmers Club, willingness to assist with installing WASH facilities and set up Water Committees and payment for maintenance and repairs.", + "title": "Water for Bilibiza" +``` + +#### Map for Organisation Resource + +We have created a resource that provides all the information about organisations that is needed to visualise them on a map including population of an info window that gives extra information about the point selected. This includes the location, title and image information. + +http://rsr.akvo.org/api/v1/map_for_organisation/?format=json + +This resource contains the following information: + +``` + "absolute_url": "/organisation/411/", + "allow_edit": true, + "contact_email": "info@1procentclub.nl", + "contact_person": "Bart Lacroix", + "created_at": true, + "fax": "", + "iati_org_id": null, + "id": 411, + "language": "en", + "last_modified_at": true, + "logo": { + "original": "/media/db/org/411/Organisation_411_logo_2011-09-05_14.11.13.png", + "thumbnails": { + "fb_thumb": "/media/db/org/411/Organisation_411_logo_2011-09-05_14.11.13_png_200x200_pad_q85.jpg", + "map_thumb": "/media/db/org/411/Organisation_411_logo_2011-09-05_14.11.13_png_160x120_autocrop_q85.jpg" + } + }, + "long_name": "Stichting 1%CLUB", + "mobile": "", + "name": "1%Club", + "new_organisation_type": 22, + "notes": "", + "organisation_type": "N", + "phone": "", + "primary_location": { + "address_1": "'s-Gravenhekje 1A", + "address_2": "", + "city": "Amsterdam", + "country": "/api/v1/country/3/", + "id": 1, + "latitude": 52.3723, + "longitude": 4.907987, + "postcode": "1011 TG", + "primary": true, + "resource_uri": "/api/v1/organisation_map_location/1/", + "state": "Noord-Holland" + }, + "resource_uri": "/api/v1/map_for_organisation/411/", + "url": "http://www.onepercentclub.com/" +``` + + +#### Project Map Location + +This resource provides only the basic location information about projects that allow points to be populated on a map. No further contextual information is provided within this resource. + +http://rsr.akvo.org/api/v1/project_map_location/?format=json + +This resource contains the following information: + +``` + "address_1": "", + "address_2": "", + "city": "", + "country": "/api/v1/country/13/", + "id": 4969, + "latitude": 48.878416, + "longitude": 2.320922, + "postcode": "", + "primary": true, + "resource_uri": "/api/v1/project_map_location/4969/", + "state": "" +``` + + +#### Organisation Map Location + +This resource provides only the basic location information about organisations that allow points to be populated on a map. No further contextual information is provided within this resource. + +http://rsr.akvo.org/api/v1/organisation_map_location/?format=json + +This resource contains the following information: + +``` + "address_1": "36 Beaumont Street", + "address_2": "", + "city": "Oxford", + "country": "/api/v1/country/39/", + "id": 1972, + "latitude": 51.755095, + "longitude": -1.260939, + "postcode": "OX1 2PG", + "primary": true, + "resource_uri": "/api/v1/organisation_map_location/1972/", + "state": "" +``` + +#### Right now in Akvo + +This resouce provides the high level figures that are presented within our overview at Akvo.org. + +http://rsr.akvo.org/api/v1/right_now_in_akvo/?format=json + +This resource contains the following information: + +``` + "number_of_organisations": 1883, + "number_of_projects": 1773, + "people_served": 4025000, + "projects_budget_millions": 868.8 +``` \ No newline at end of file From bdecec176967647a519088532433b0d45d242baf Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Wed, 18 Jun 2014 13:46:44 +0200 Subject: [PATCH 07/26] [#141] Preliminary commit --- akvo/rsr/admin.py | 40 ++++++++++++++++++++------------------ akvo/rsr/models.py | 48 +++++++--------------------------------------- 2 files changed, 28 insertions(+), 60 deletions(-) diff --git a/akvo/rsr/admin.py b/akvo/rsr/admin.py index f48e9b1bf9..5fe0967b90 100644 --- a/akvo/rsr/admin.py +++ b/akvo/rsr/admin.py @@ -72,29 +72,30 @@ def get_readonly_fields(self, request, obj=None): admin.site.register(get_model('rsr', 'country'), CountryAdmin) -class RSR_LocationFormFormSet(forms.models.BaseInlineFormSet): - def clean(self): - if self.forms: - # keep track of how many non-deleted forms we have and how many primary locations are ticked - form_count = primary_count = 0 - for form in self.forms: - if form.is_valid() and not form.cleaned_data.get('DELETE', False): - form_count += 1 - try: - primary_count += 1 if form.cleaned_data['primary'] else 0 - except: - pass - # if we have any forms left there must be exactly 1 primary location - if form_count > 0 and not primary_count == 1: - self._non_form_errors = ErrorList([ - _(u'The project must have exactly one filled in primary location if any locations at all are to be included') - ]) +# class RSR_LocationFormFormSet(forms.models.BaseInlineFormSet): +# def clean(self): +# if self.forms: +# # keep track of how many non-deleted forms we have and how many primary locations are ticked +# form_count = primary_count = 0 +# for form in self.forms: +# if form.is_valid() and not form.cleaned_data.get('DELETE', False): +# form_count += 1 +# try: +# primary_count += 1 if form.cleaned_data['primary'] else 0 +# except: +# pass +# # if we have any forms left there must be exactly 1 primary location +# if form_count > 0 and not primary_count == 1: +# self._non_form_errors = ErrorList([ +# _(u'The project must have exactly one filled in primary location if any locations at all are to be included') +# ]) class OrganisationLocationInline(admin.StackedInline): model = get_model('rsr', 'organisationlocation') extra = 0 - formset = RSR_LocationFormFormSet +# formset = RSR_LocationFormFormSet + exclude = ['primary',] class InternalOrganisationIDAdmin(admin.ModelAdmin): @@ -376,7 +377,8 @@ def get_formset(self, request, *args, **kwargs): class ProjectLocationInline(admin.StackedInline): model = get_model('rsr', 'projectlocation') extra = 0 - formset = RSR_LocationFormFormSet +# formset = RSR_LocationFormFormSet + exclude = ['primary',] class ProjectAdmin(TimestampsAdminDisplayMixin, admin.ModelAdmin): diff --git a/akvo/rsr/models.py b/akvo/rsr/models.py index 659d407e1f..d6ab392e17 100644 --- a/akvo/rsr/models.py +++ b/akvo/rsr/models.py @@ -121,39 +121,6 @@ class Meta: ordering = ['name'] -#class Location(models.Model): -# _help_text = _(u"Go to iTouchMap.com " -# u'to get the decimal coordinates of your project.') -# latitude = LatitudeField(_(u'latitude'), default=0, help_text=_help_text) -# longitude = LongitudeField(_(u'longitude'), default=0, help_text=_help_text) -# city = ValidXMLCharField(_(u'city'), blank=True, max_length=255, help_text=_('(255 characters).')) -# state = ValidXMLCharField(_(u'state'), blank=True, max_length=255, help_text=_('(255 characters).')) -# country = models.ForeignKey(Country, verbose_name=_(u'country')) -# address_1 = ValidXMLCharField(_(u'address 1'), max_length=255, blank=True, help_text=_('(255 characters).')) -# address_2 = ValidXMLCharField(_(u'address 2'), max_length=255, blank=True, help_text=_('(255 characters).')) -# postcode = ValidXMLCharField(_(u'postcode'), max_length=10, blank=True, help_text=_('(10 characters).')) -# content_type = models.ForeignKey(ContentType) -# object_id = models.PositiveIntegerField() -# content_object = generic.GenericForeignKey('content_type', 'object_id') -# primary = models.BooleanField(_(u'primary location'), default=True) -# -# def __unicode__(self): -# return u'%s, %s (%s)' % (self.city, self.state, self.country) -# -# def save(self, *args, **kwargs): -# if self.primary: -# qs = Location.objects.filter(content_type=self.content_type, -# object_id=self.object_id, primary=True) -# if self.pk: -# qs = qs.exclude(pk=self.pk) -# if qs.count() != 0: -# self.primary = False -# super(Location, self).save(*args, **kwargs) -# -# class Meta: -# ordering = ['-primary',] - - class BaseLocation(models.Model): _help_text = _(u"Go to iTouchMap.com " u'to get the decimal coordinates of your project.') @@ -165,20 +132,19 @@ class BaseLocation(models.Model): address_1 = ValidXMLCharField(_(u'address 1'), max_length=255, blank=True, help_text=_('(255 characters).')) address_2 = ValidXMLCharField(_(u'address 2'), max_length=255, blank=True, help_text=_('(255 characters).')) postcode = ValidXMLCharField(_(u'postcode'), max_length=10, blank=True, help_text=_('(10 characters).')) - primary = models.BooleanField(_(u'primary location'), db_index=True, default=True) - -# def __unicode__(self): -# return u'%s, %s (%s)' % (self.city, self.state, self.country) + primary = models.BooleanField(_(u'primary location'), db_index=True, default=False) def save(self, *args, **kwargs): - super(BaseLocation, self).save(*args, **kwargs) + location_target = self.location_target + if self.primary: - location_target = self.location_target - # this is probably redundant since the admin form saving should handle this - # but if we ever save a location from other code it's an extra safety location_target.locations.exclude(pk__exact=self.pk).update(primary=False) location_target.primary_location = self location_target.save() + else: + #TODO + + super(BaseLocation, self).save(*args, **kwargs) class Meta: abstract = True From b298b81ff82820b7d026d3547313250d7d936d4f Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Wed, 18 Jun 2014 16:25:42 +0200 Subject: [PATCH 08/26] [#141] Primary location checkbox removed from admin --- akvo/rsr/admin.py | 21 --------------------- akvo/rsr/models.py | 29 +++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/akvo/rsr/admin.py b/akvo/rsr/admin.py index 5fe0967b90..785a7aa008 100644 --- a/akvo/rsr/admin.py +++ b/akvo/rsr/admin.py @@ -72,29 +72,9 @@ def get_readonly_fields(self, request, obj=None): admin.site.register(get_model('rsr', 'country'), CountryAdmin) -# class RSR_LocationFormFormSet(forms.models.BaseInlineFormSet): -# def clean(self): -# if self.forms: -# # keep track of how many non-deleted forms we have and how many primary locations are ticked -# form_count = primary_count = 0 -# for form in self.forms: -# if form.is_valid() and not form.cleaned_data.get('DELETE', False): -# form_count += 1 -# try: -# primary_count += 1 if form.cleaned_data['primary'] else 0 -# except: -# pass -# # if we have any forms left there must be exactly 1 primary location -# if form_count > 0 and not primary_count == 1: -# self._non_form_errors = ErrorList([ -# _(u'The project must have exactly one filled in primary location if any locations at all are to be included') -# ]) - - class OrganisationLocationInline(admin.StackedInline): model = get_model('rsr', 'organisationlocation') extra = 0 -# formset = RSR_LocationFormFormSet exclude = ['primary',] @@ -377,7 +357,6 @@ def get_formset(self, request, *args, **kwargs): class ProjectLocationInline(admin.StackedInline): model = get_model('rsr', 'projectlocation') extra = 0 -# formset = RSR_LocationFormFormSet exclude = ['primary',] diff --git a/akvo/rsr/models.py b/akvo/rsr/models.py index d6ab392e17..a7d3ee92e7 100644 --- a/akvo/rsr/models.py +++ b/akvo/rsr/models.py @@ -134,15 +134,36 @@ class BaseLocation(models.Model): postcode = ValidXMLCharField(_(u'postcode'), max_length=10, blank=True, help_text=_('(10 characters).')) primary = models.BooleanField(_(u'primary location'), db_index=True, default=False) + def delete(self, *args, **kwargs): + location_target = self.location_target + other_locations = [loc for loc in location_target.locations.exclude(pk__exact=self.pk)] + + if self.primary and len(other_locations) > 0: + primary_loc = other_locations.pop(0) + primary_loc.primary = True + primary_loc.save() + location_target.locations.exclude(pk__exact=primary_loc.pk).update(primary=False) + location_target.primary_location = primary_loc + location_target.save() + + super(BaseLocation, self).delete(*args, **kwargs) + def save(self, *args, **kwargs): location_target = self.location_target + primaries = [loc for loc in location_target.locations.exclude(pk__exact=self.pk) if loc.primary] if self.primary: - location_target.locations.exclude(pk__exact=self.pk).update(primary=False) - location_target.primary_location = self - location_target.save() + primary_loc = self else: - #TODO + if len(primaries) == 0: + self.primary = True + primary_loc = self + elif len(primaries) > 0: + primary_loc = primaries.pop(0) + + location_target.locations.exclude(pk__exact=primary_loc.pk).update(primary=False) + location_target.primary_location = primary_loc + location_target.save() super(BaseLocation, self).save(*args, **kwargs) From d7677032f4a6718e69b754b481fd8748adc49724 Mon Sep 17 00:00:00 2001 From: Adrian Collier Date: Tue, 24 Jun 2014 09:27:38 +0200 Subject: [PATCH 09/26] #510 fixed links for wrong branch --- docs/RSR Features/API/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/RSR Features/API/README.md b/docs/RSR Features/API/README.md index 7e75fecc49..1a7f30803f 100644 --- a/docs/RSR Features/API/README.md +++ b/docs/RSR Features/API/README.md @@ -4,11 +4,11 @@ In addition to the manual interaction methods in RSR, we have built a data API t Firstly, we offer the ability to READ all of your data directly. The API provides information in JSON or XML format that can be parsed and implemented directly into a Website or data store. This allows your data to be displayed in any custom format you wish. Additionally you can choose to display, all or part of the available information with no impact on the data entry or maintenance processes. -We are already working with this solution for some time. You can find a list of our Live Partners using this solution here: [API Partners](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/API-Read-Partners.md). +We are already working with this solution for some time. You can find a list of our Live Partners using this solution here: [API Partners](https://github.com/akvo/akvo-rsr/blob/master/docs/RSR%20Features/API/API-Read-Partners.md). -If you are interested in using the READ API for displaying your RSR Projects in your Site or Page, please check out our [Technical API Documentation](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/Akvo-RSR-API-developer-documentation.md). +If you are interested in using the READ API for displaying your RSR Projects in your Site or Page, please check out our [Technical API Documentation](https://github.com/akvo/akvo-rsr/blob/master/docs/RSR%20Features/API/Akvo-RSR-API-developer-documentation.md). -In addition to the API Technical Documentation, we're also working on expanding the list of customised resources created with specific data sets to be visualised. This work is captured within our [API Resources documentation](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/API-Resources.md). +In addition to the API Technical Documentation, we're also working on expanding the list of customised resources created with specific data sets to be visualised. This work is captured within our [API Resources documentation](https://github.com/akvo/akvo-rsr/blob/master/docs/RSR%20Features/API/API-Resources.md). ### Creating Data using the RSR API @@ -17,11 +17,11 @@ Secondly we have created the ability for Partners to be able to export their pro This product is still being improved and fine-tuned, but it is Live and is available to use. -The file specification and requirements are documented and can be found [in this folder](https://github.com/akvo/akvo-rsr/tree/wiki-docs/docs/RSR%20Features/API/downloads). +The file specification and requirements are documented and can be found [in this folder](https://github.com/akvo/akvo-rsr/tree/master/docs/RSR%20Features/API/downloads). We have also documented some of the specific Partner customisations for our implementations within these pages: -- [Cordaid Foundation NL](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/API-Write---Cordaid-Implementation.md)() -- [RAIN Foundation NL](https://github.com/akvo/akvo-rsr/blob/wiki-docs-adrian-2/docs/RSR%20Features/API/API-Write-RAIN-Implementation.md) +- [Cordaid Foundation NL](https://github.com/akvo/akvo-rsr/blob/master/docs/RSR%20Features/API/API-Write---Cordaid-Implementation.md)() +- [RAIN Foundation NL](https://github.com/akvo/akvo-rsr/blob/master/docs/RSR%20Features/API/API-Write-RAIN-Implementation.md) At present this option requires a full implementation project with responsibilities from all parties involved. If you would like to find out more about what this involves and if this is available for your Organisation, please contact us at support@akvo.org. \ No newline at end of file From 696c72041dbac9c8e5f36428d0fbda8d07f23a6a Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Tue, 24 Jun 2014 15:35:09 +0200 Subject: [PATCH 10/26] [#620] Added keyword models and admin entries --- akvo/rsr/admin.py | 31 +- akvo/rsr/migrations/0054_add_model_keyword.py | 425 ++++++++++++++++++ akvo/rsr/models.py | 38 ++ .../admin/rsr/project/change_form.html | 5 + 4 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 akvo/rsr/migrations/0054_add_model_keyword.py diff --git a/akvo/rsr/admin.py b/akvo/rsr/admin.py index f48e9b1bf9..80abb57870 100644 --- a/akvo/rsr/admin.py +++ b/akvo/rsr/admin.py @@ -373,6 +373,11 @@ def get_formset(self, request, *args, **kwargs): return formset +class ProjectKeywordInline(admin.TabularInline): + model = get_model('rsr', 'ProjectKeyword') + extra = 0 + + class ProjectLocationInline(admin.StackedInline): model = get_model('rsr', 'projectlocation') extra = 0 @@ -382,7 +387,7 @@ class ProjectLocationInline(admin.StackedInline): class ProjectAdmin(TimestampsAdminDisplayMixin, admin.ModelAdmin): model = get_model('rsr', 'project') inlines = ( - GoalInline, ProjectLocationInline, BudgetItemAdminInLine, BenchmarkInline, PartnershipInline, LinkInline, + GoalInline, ProjectLocationInline, BudgetItemAdminInLine, BenchmarkInline, PartnershipInline, LinkInline, ProjectKeywordInline ) save_as = True @@ -441,6 +446,12 @@ class ProjectAdmin(TimestampsAdminDisplayMixin, admin.ModelAdmin): ), 'fields': ('notes',), }), + (_(u'Keywords'), { + 'description': u'

%s

' % _( + u'Add keywords belonging to your project. These keywords must be existing already in Akvo RSR. If you want to use a keyword that does not exist in the system, please contact support@akvo.org.' + ), + 'fields': (), + }), ) list_display = ('id', 'title', 'status', 'project_plan_summary', 'latest_update', 'show_current_image', 'is_published',) @@ -811,8 +822,14 @@ class PaymentGatewaySelectorAdmin(admin.ModelAdmin): admin.site.register(get_model('rsr', 'paymentgatewayselector'), PaymentGatewaySelectorAdmin) +class PartnerSiteKeywordInline(admin.TabularInline): + model = get_model('rsr', 'PartnerSiteKeyword') + extra = 0 + + class PartnerSiteAdmin(TimestampsAdminDisplayMixin, admin.ModelAdmin): form = PartnerSiteAdminForm + inlines = (PartnerSiteKeywordInline,) fieldsets = ( # the 'notes' field is added in get_fieldsets() for eligible users (u'General', dict(fields=('organisation', 'enabled',))), @@ -821,6 +838,12 @@ class PartnerSiteAdmin(TimestampsAdminDisplayMixin, admin.ModelAdmin): dict(fields=('about_box', 'about_image', 'custom_css', 'custom_logo', 'custom_favicon',))), (u'Languages and translation', dict(fields=('default_language', 'ui_translation', 'google_translation',))), (u'Social', dict(fields=('twitter_button', 'facebook_button', 'facebook_app_id',))), + (_(u'Projects'), { + 'description': u'

%s

' % _( + u'Choose what projects will be shown on your partnersite. By selecting one or more keywords, only projects matching that keyword will be shown on the partnersite.' + ), + 'fields': ('partner_projects',), + }), ) list_display = '__unicode__', 'full_domain', 'enabled', # created_at and last_modified_at MUST be readonly since they have the auto_now/_add attributes @@ -888,3 +911,9 @@ def has_change_permission(self, request, obj=None): return False admin.site.register(get_model('rsr', 'partnersite'), PartnerSiteAdmin) + +class KeywordAdmin(admin.ModelAdmin): + model = get_model('rsr', 'Keyword') + list_display = ('label',) + +admin.site.register(get_model('rsr', 'Keyword'), KeywordAdmin) diff --git a/akvo/rsr/migrations/0054_add_model_keyword.py b/akvo/rsr/migrations/0054_add_model_keyword.py new file mode 100644 index 0000000000..d9923c08fe --- /dev/null +++ b/akvo/rsr/migrations/0054_add_model_keyword.py @@ -0,0 +1,425 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Keyword' + db.create_table('rsr_keyword', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('label', self.gf('akvo.rsr.fields.ValidXMLCharField')(max_length=30, unique=True, db_index=True)) + )) + db.send_create_signal('rsr', ['Keyword']) + + # Adding model 'ProjectKeyword' + db.create_table('rsr_projectkeyword', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('keyword', self.gf('django.db.models.fields.related.ForeignKey')(related_name='targets', + null=True, + to=orm['rsr.Keyword'])), + ('keyword_target', self.gf('django.db.models.fields.related.ForeignKey')(related_name='keywords', + null=True, + to=orm['rsr.Project'])) + )) + db.send_create_signal('rsr', ['ProjectKeyword']) + + # Adding model 'PartnerSiteKeyword' + db.create_table('rsr_partnersitekeyword', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('keyword', self.gf('django.db.models.fields.related.ForeignKey')(related_name='targets', + null=True, + to=orm['rsr.Keyword'])), + ('keyword_target', self.gf('django.db.models.fields.related.ForeignKey')(related_name='keywords', + null=True, + to=orm['rsr.PartnerSite'])) + )) + db.send_create_signal('rsr', ['PartnerSiteKeyword']) + + # Adding field 'PartnerSite.partner_projects' + db.add_column('rsr_partnersite', 'partner_projects', + self.gf('django.db.models.fields.BooleanField')(default=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting model 'Keyword' + db.delete_table('rsr_keyword') + + # Deleting model 'ProjectKeyword' + db.delete_table('rsr_projectkeyword') + + # Deleting model 'PartnerSiteKeyword' + db.delete_table('rsr_partnersitekeyword') + + # Deleting field 'PartnerSite.partner_projects' + db.delete_column('rsr_partnersite', 'partner_projects') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'rsr.benchmark': { + 'Meta': {'ordering': "('category__name', 'name__order')", 'object_name': 'Benchmark'}, + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.Category']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.Benchmarkname']"}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'benchmarks'", 'to': u"orm['rsr.Project']"}), + 'value': ('django.db.models.fields.IntegerField', [], {}) + }, + u'rsr.benchmarkname': { + 'Meta': {'ordering': "['order', 'name']", 'object_name': 'Benchmarkname'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '80'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + u'rsr.budgetitem': { + 'Meta': {'ordering': "('label',)", 'unique_together': "(('project', 'label'),)", 'object_name': 'BudgetItem'}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.BudgetItemLabel']"}), + 'other_extra': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'budget_items'", 'to': u"orm['rsr.Project']"}) + }, + u'rsr.budgetitemlabel': { + 'Meta': {'ordering': "('label',)", 'object_name': 'BudgetItemLabel'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('akvo.rsr.fields.ValidXMLCharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}) + }, + u'rsr.category': { + 'Meta': {'ordering': "['name']", 'object_name': 'Category'}, + 'benchmarknames': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['rsr.Benchmarkname']", 'symmetrical': 'False', 'blank': 'True'}), + 'focus_area': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'categories'", 'symmetrical': 'False', 'to': u"orm['rsr.FocusArea']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '50', 'db_index': 'True'}) + }, + u'rsr.country': { + 'Meta': {'ordering': "['name']", 'object_name': 'Country'}, + 'continent': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '20', 'db_index': 'True'}), + 'continent_code': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '2', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'iso_code': ('akvo.rsr.fields.ValidXMLCharField', [], {'unique': 'True', 'max_length': '2', 'db_index': 'True'}), + 'name': ('akvo.rsr.fields.ValidXMLCharField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}) + }, + u'rsr.focusarea': { + 'Meta': {'ordering': "['name']", 'object_name': 'FocusArea'}, + 'description': ('akvo.rsr.fields.ValidXMLTextField', [], {'max_length': '500'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'link_to': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'name': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '50'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}) + }, + u'rsr.goal': { + 'Meta': {'object_name': 'Goal'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'goals'", 'to': u"orm['rsr.Project']"}), + 'text': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '100', 'blank': 'True'}) + }, + u'rsr.internalorganisationid': { + 'Meta': {'unique_together': "(('recording_org', 'referenced_org'),)", 'object_name': 'InternalOrganisationID'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'identifier': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '200'}), + 'recording_org': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'internal_ids'", 'to': u"orm['rsr.Organisation']"}), + 'referenced_org': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reference_ids'", 'to': u"orm['rsr.Organisation']"}) + }, + u'rsr.invoice': { + 'Meta': {'ordering': "['-id']", 'object_name': 'Invoice'}, + 'amount': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'amount_received': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '10', 'decimal_places': '2', 'blank': 'True'}), + 'bank': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '4', 'blank': 'True'}), + 'campaign_code': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '15', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'engine': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'paypal'", 'max_length': '10'}), + 'http_referer': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ipn': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'notes': ('akvo.rsr.fields.ValidXMLTextField', [], {'default': "''", 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invoices'", 'to': u"orm['rsr.Project']"}), + 'status': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}), + 'test': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'transaction_id': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '100', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + u'rsr.keyword': { + 'Meta': {'ordering': "['label',]", 'object_name': 'Keyword'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30', 'db_index': 'True'}) + }, + u'rsr.link': { + 'Meta': {'object_name': 'Link'}, + 'caption': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '50'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'kind': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '1'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'links'", 'to': u"orm['rsr.Project']"}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) + }, + u'rsr.minicms': { + 'Meta': {'ordering': "['-active', '-id']", 'object_name': 'MiniCMS'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feature_box': ('akvo.rsr.fields.ValidXMLTextField', [], {'max_length': '350'}), + 'feature_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '50'}), + 'lower_height': ('django.db.models.fields.IntegerField', [], {'default': '500'}), + 'top_right_box': ('akvo.rsr.fields.ValidXMLTextField', [], {'max_length': '350'}) + }, + u'rsr.molliegateway': { + 'Meta': {'object_name': 'MollieGateway'}, + 'currency': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'EUR'", 'max_length': '3'}), + 'description': ('akvo.rsr.fields.ValidXMLTextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255'}), + 'notification_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'partner_id': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '10'}) + }, + u'rsr.organisation': { + 'Meta': {'ordering': "['name']", 'object_name': 'Organisation'}, + 'allow_edit': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'contact_email': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '50', 'blank': 'True'}), + 'contact_person': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '30', 'blank': 'True'}), + 'content_owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.Organisation']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('akvo.rsr.fields.ValidXMLTextField', [], {'blank': 'True'}), + 'fax': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '20', 'blank': 'True'}), + 'iati_org_id': ('akvo.rsr.fields.ValidXMLCharField', [], {'db_index': 'True', 'max_length': '75', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_org_ids': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'recording_organisation'", 'symmetrical': 'False', 'through': u"orm['rsr.InternalOrganisationID']", 'to': u"orm['rsr.Organisation']"}), + 'language': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'en'", 'max_length': '2'}), + 'last_modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'logo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'long_name': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '75', 'blank': 'True'}), + 'mobile': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '20', 'blank': 'True'}), + 'name': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '25', 'db_index': 'True'}), + 'new_organisation_type': ('django.db.models.fields.IntegerField', [], {'default': '22', 'db_index': 'True'}), + 'notes': ('akvo.rsr.fields.ValidXMLTextField', [], {'default': "''", 'blank': 'True'}), + 'organisation_type': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '1', 'db_index': 'True'}), + 'partner_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['rsr.PartnerType']", 'symmetrical': 'False'}), + 'phone': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '20', 'blank': 'True'}), + 'primary_location': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.OrganisationLocation']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + u'rsr.organisationaccount': { + 'Meta': {'object_name': 'OrganisationAccount'}, + 'account_level': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'free'", 'max_length': '12'}), + 'organisation': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['rsr.Organisation']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'rsr.organisationlocation': { + 'Meta': {'ordering': "['-primary']", 'object_name': 'OrganisationLocation'}, + 'address_1': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}), + 'address_2': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}), + 'city': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.Country']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'latitude': ('akvo.rsr.fields.LatitudeField', [], {'default': '0', 'db_index': 'True'}), + 'location_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'locations'", 'null': 'True', 'to': u"orm['rsr.Organisation']"}), + 'longitude': ('akvo.rsr.fields.LongitudeField', [], {'default': '0', 'db_index': 'True'}), + 'postcode': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '10', 'blank': 'True'}), + 'primary': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'state': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'rsr.partnership': { + 'Meta': {'ordering': "['partner_type']", 'object_name': 'Partnership'}, + 'funding_amount': ('django.db.models.fields.DecimalField', [], {'db_index': 'True', 'null': 'True', 'max_digits': '10', 'decimal_places': '2', 'blank': 'True'}), + 'iati_activity_id': ('akvo.rsr.fields.ValidXMLCharField', [], {'db_index': 'True', 'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'iati_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_id': ('akvo.rsr.fields.ValidXMLCharField', [], {'db_index': 'True', 'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'organisation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'partnerships'", 'to': u"orm['rsr.Organisation']"}), + 'partner_type': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '8', 'db_index': 'True'}), + 'partner_type_extra': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'partnerships'", 'to': u"orm['rsr.Project']"}) + }, + u'rsr.partnersite': { + 'Meta': {'ordering': "('organisation__name',)", 'object_name': 'PartnerSite'}, + 'about_box': ('akvo.rsr.fields.ValidXMLTextField', [], {'max_length': '500', 'blank': 'True'}), + 'about_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'cname': ('akvo.rsr.fields.NullCharField', [], {'max_length': '100', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'custom_css': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'custom_favicon': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'custom_logo': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'custom_return_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'custom_return_url_text': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}), + 'default_language': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'en'", 'max_length': '5'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'facebook_app_id': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'facebook_button': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'google_translation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'hostname': ('akvo.rsr.fields.ValidXMLCharField', [], {'unique': 'True', 'max_length': '50'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'keywords': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'partnersites'", 'through': u"orm['rsr.PartnerSiteKeyword']", 'to': u"orm['rsr.Keyword']"}), + 'last_modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'notes': ('akvo.rsr.fields.ValidXMLTextField', [], {'default': "''", 'blank': 'True'}), + 'organisation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.Organisation']"}), + 'partner_projects': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'twitter_button': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'ui_translation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'rsr.partnersitekeyword': { + 'Meta': {'ordering': "('-id')", 'object_name': 'PartnerSiteKeyword'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'keyword': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'targets'", 'null': 'True', 'to': u"orm['rsr.Keyword']"}), + 'keyword_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'partnersitekeywords'", 'null': 'True', 'to': u"orm['rsr.PartnerSite']"}) + }, + u'rsr.partnertype': { + 'Meta': {'ordering': "('label',)", 'object_name': 'PartnerType'}, + 'id': ('akvo.rsr.fields.ValidXMLCharField', [], {'unique': 'True', 'max_length': '8', 'primary_key': 'True'}), + 'label': ('akvo.rsr.fields.ValidXMLCharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'rsr.paymentgatewayselector': { + 'Meta': {'object_name': 'PaymentGatewaySelector'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mollie_gateway': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': u"orm['rsr.MollieGateway']"}), + 'paypal_gateway': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': u"orm['rsr.PayPalGateway']"}), + 'project': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['rsr.Project']", 'unique': 'True'}) + }, + u'rsr.paypalgateway': { + 'Meta': {'object_name': 'PayPalGateway'}, + 'account_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'currency': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'EUR'", 'max_length': '3'}), + 'description': ('akvo.rsr.fields.ValidXMLTextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'locale': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'US'", 'max_length': '2'}), + 'name': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255'}), + 'notification_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}) + }, + u'rsr.project': { + 'Meta': {'ordering': "['-id']", 'object_name': 'Project'}, + 'background': ('akvo.rsr.fields.ProjectLimitedTextField', [], {'blank': 'True'}), + 'budget': ('django.db.models.fields.DecimalField', [], {'decimal_places': '2', 'default': '0', 'max_digits': '10', 'blank': 'True', 'null': 'True', 'db_index': 'True'}), + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'to': u"orm['rsr.Category']"}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'currency': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'EUR'", 'max_length': '3'}), + 'current_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'current_image_caption': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '50', 'blank': 'True'}), + 'current_image_credit': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '50', 'blank': 'True'}), + 'current_status': ('akvo.rsr.fields.ProjectLimitedTextField', [], {'blank': 'True'}), + 'date_complete': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_request_posted': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today'}), + 'donate_button': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'funds': ('django.db.models.fields.DecimalField', [], {'decimal_places': '2', 'default': '0', 'max_digits': '10', 'blank': 'True', 'null': 'True', 'db_index': 'True'}), + 'funds_needed': ('django.db.models.fields.DecimalField', [], {'decimal_places': '2', 'default': '0', 'max_digits': '10', 'blank': 'True', 'null': 'True', 'db_index': 'True'}), + 'goals_overview': ('akvo.rsr.fields.ProjectLimitedTextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'keywords': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'through': u"orm['rsr.ProjectKeyword']", 'to': u"orm['rsr.Keyword']"}), + 'language': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'en'", 'max_length': '2'}), + 'last_modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'notes': ('akvo.rsr.fields.ValidXMLTextField', [], {'default': "''", 'blank': 'True'}), + 'partners': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'through': u"orm['rsr.Partnership']", 'to': u"orm['rsr.Organisation']"}), + 'primary_location': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.ProjectLocation']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'project_plan': ('akvo.rsr.fields.ValidXMLTextField', [], {'blank': 'True'}), + 'project_plan_summary': ('akvo.rsr.fields.ProjectLimitedTextField', [], {}), + 'project_rating': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'status': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'N'", 'max_length': '1', 'db_index': 'True'}), + 'subtitle': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '75'}), + 'sustainability': ('akvo.rsr.fields.ValidXMLTextField', [], {}), + 'sync_owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.Organisation']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'target_group': ('akvo.rsr.fields.ProjectLimitedTextField', [], {'blank': 'True'}), + 'title': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '45', 'db_index': 'True'}) + }, + u'rsr.projectcomment': { + 'Meta': {'ordering': "('-id',)", 'object_name': 'ProjectComment'}, + 'comment': ('akvo.rsr.fields.ValidXMLTextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': u"orm['rsr.Project']"}), + 'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'rsr.projectkeyword': { + 'Meta': {'ordering': "('-id')", 'object_name': 'ProjectKeyword'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'keyword': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'targets'", 'null': 'True', 'to': u"orm['rsr.Keyword']"}), + 'keyword_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectkeywords'", 'null': 'True', 'to': u"orm['rsr.Project']"}) + }, + u'rsr.projectlocation': { + 'Meta': {'ordering': "['-primary']", 'object_name': 'ProjectLocation'}, + 'address_1': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}), + 'address_2': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}), + 'city': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.Country']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'latitude': ('akvo.rsr.fields.LatitudeField', [], {'default': '0', 'db_index': 'True'}), + 'location_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'locations'", 'null': 'True', 'to': u"orm['rsr.Project']"}), + 'longitude': ('akvo.rsr.fields.LongitudeField', [], {'default': '0', 'db_index': 'True'}), + 'postcode': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '10', 'blank': 'True'}), + 'primary': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'state': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'rsr.projectupdate': { + 'Meta': {'ordering': "['-id']", 'object_name': 'ProjectUpdate'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'en'", 'max_length': '2'}), + 'last_modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'notes': ('akvo.rsr.fields.ValidXMLTextField', [], {'default': "''", 'blank': 'True'}), + 'photo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'photo_caption': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '75', 'blank': 'True'}), + 'photo_credit': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '25', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': u"orm['rsr.Project']"}), + 'text': ('akvo.rsr.fields.ValidXMLTextField', [], {'blank': 'True'}), + 'title': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '50', 'db_index': 'True'}), + 'update_method': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'W'", 'max_length': '1', 'db_index': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'user_agent': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), + 'uuid': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "''", 'max_length': '40', 'db_index': 'True', 'blank': 'True'}), + 'video': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'video_caption': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '75', 'blank': 'True'}), + 'video_credit': ('akvo.rsr.fields.ValidXMLCharField', [], {'max_length': '25', 'blank': 'True'}) + }, + u'rsr.publishingstatus': { + 'Meta': {'ordering': "('-status', 'project')", 'object_name': 'PublishingStatus'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['rsr.Project']", 'unique': 'True'}), + 'status': ('akvo.rsr.fields.ValidXMLCharField', [], {'default': "'unpublished'", 'max_length': '30'}) + }, + u'rsr.userprofile': { + 'Meta': {'ordering': "['user__username']", 'object_name': 'UserProfile'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notes': ('akvo.rsr.fields.ValidXMLTextField', [], {'default': "''", 'blank': 'True'}), + 'organisation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['rsr.Organisation']"}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + } + } + + complete_apps = ['rsr'] \ No newline at end of file diff --git a/akvo/rsr/models.py b/akvo/rsr/models.py index c9d86530c7..a410b8824e 100644 --- a/akvo/rsr/models.py +++ b/akvo/rsr/models.py @@ -195,6 +195,28 @@ class ProjectLocation(BaseLocation): location_target = models.ForeignKey('Project', null=True, related_name='locations') +class ProjectKeyword(models.Model): + keyword = models.ForeignKey('Keyword', null=True, related_name='keywordprojects') + keyword_target = models.ForeignKey('Project', null=True, related_name='projectkeywords') + + def __unicode__(self): + return self.keyword.label + + class Meta: + ordering = ['-id',] + + +class PartnerSiteKeyword(models.Model): + keyword = models.ForeignKey('Keyword', null=True, related_name='keywordpartnersites') + keyword_target = models.ForeignKey('PartnerSite', null=True, related_name='partnersitekeywords') + + def __unicode__(self): + return self.keyword.label + + class Meta: + ordering = ['-id',] + + class PartnerType(models.Model): id = ValidXMLCharField(max_length=8, primary_key=True, unique=True) label = ValidXMLCharField(max_length=30, unique=True) @@ -718,6 +740,18 @@ def get_queryset(self): return self.model.OrganisationsQuerySet(self.model) +class Keyword(models.Model): + label = ValidXMLCharField(_(u'label'), max_length=30, unique=True, db_index=True) + + def __unicode__(self): + return self.label + + class Meta: + ordering = ('label',) + verbose_name = _(u'keyword') + verbose_name_plural = _(u'keywords') + + class Project(TimestampsMixin, models.Model): def image_path(instance, file_name): return rsr_image_path(instance, file_name, 'db/project/%(instance_pk)s/%(file_name)s') @@ -759,6 +793,7 @@ def image_path(instance, file_name): language = ValidXMLCharField(max_length=2, choices=settings.LANGUAGES, default='en', help_text=u'The main language of the project') project_rating = models.IntegerField(_(u'project rating'), default=0) notes = ValidXMLTextField(_(u'notes'), blank=True, default='', help_text=_(u'(Unlimited number of characters).')) + keywords = models.ManyToManyField(Keyword, verbose_name=_(u'keywords'), through=ProjectKeyword, related_name='projects',) # budget currency = ValidXMLCharField(_(u'currency'), choices=CURRENCY_CHOICES, max_length=3, default='EUR') @@ -1868,6 +1903,9 @@ def custom_logo_path(instance, filename): u'Follow the instructions here' ) ) + partner_projects = models.BooleanField(_(u'Show only projects of partner'), default=True, + help_text=_(u'Uncheck to list all projects on this partnersite.')) + keywords = models.ManyToManyField(Keyword, verbose_name=_(u'keywords'), through=PartnerSiteKeyword, related_name='partnersites',) def __unicode__(self): diff --git a/akvo/templates/admin/rsr/project/change_form.html b/akvo/templates/admin/rsr/project/change_form.html index 65286f0ae8..fc0844182c 100644 --- a/akvo/templates/admin/rsr/project/change_form.html +++ b/akvo/templates/admin/rsr/project/change_form.html @@ -95,6 +95,11 @@

{% trans 'Adding and Editing Projects.' %}

{% include inline_admin_formset.opts.template %} {% endwith %} {% endif %} + {% if forloop.counter == 10 %} + {% with inline_admin_formset=inline_admin_formsets.6 %} + {% include inline_admin_formset.opts.template %} + {% endwith %} + {% endif %} {% endfor %} {% block after_field_sets %}{% endblock %} From 0d9d034f9391feb297363e0c5154151dc2d64a38 Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Tue, 24 Jun 2014 16:23:25 +0200 Subject: [PATCH 11/26] [#260] Added keyword filter logic to partner sites and widgets --- akvo/rsr/admin.py | 2 +- akvo/rsr/templatetags/maps.py | 4 +-- akvo/rsr/views_partner_sites/base.py | 18 ++++++++-- akvo/rsr/views_partner_sites/widgets.py | 36 +++++++++++++++---- .../partner_sites/widgets/projects_map.html | 2 +- 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/akvo/rsr/admin.py b/akvo/rsr/admin.py index 80abb57870..0e58669ed5 100644 --- a/akvo/rsr/admin.py +++ b/akvo/rsr/admin.py @@ -840,7 +840,7 @@ class PartnerSiteAdmin(TimestampsAdminDisplayMixin, admin.ModelAdmin): (u'Social', dict(fields=('twitter_button', 'facebook_button', 'facebook_app_id',))), (_(u'Projects'), { 'description': u'

%s

' % _( - u'Choose what projects will be shown on your partnersite. By selecting one or more keywords, only projects matching that keyword will be shown on the partnersite.' + u'Choose what projects will be shown on your partnersite. By selecting one or more keywords below, only projects matching that keyword will be shown on the partnersite.' ), 'fields': ('partner_projects',), }), diff --git a/akvo/rsr/templatetags/maps.py b/akvo/rsr/templatetags/maps.py index f4c3d93313..2be771ba6b 100644 --- a/akvo/rsr/templatetags/maps.py +++ b/akvo/rsr/templatetags/maps.py @@ -215,7 +215,7 @@ def organisation_projects_map(organisation_id, width, height, dynamic='dynamic') @register.inclusion_tag('inclusion_tags/maps.html') -def partnersite_widget_project_map(organisation_id, width, height, dynamic='dynamic'): +def partnersite_widget_project_map(projects, width, height, dynamic='dynamic'): """ params: organisation_id: id of organisation. @@ -231,8 +231,6 @@ def partnersite_widget_project_map(organisation_id, width, height, dynamic='dyna locations = [] - projects = Project.objects.filter(partnerships__organisation=organisation_id).active() - for project in projects: proj_locations = ProjectLocation.objects.filter(location_target=project) for location in proj_locations: diff --git a/akvo/rsr/views_partner_sites/base.py b/akvo/rsr/views_partner_sites/base.py index 1291c4676b..58c6c996db 100644 --- a/akvo/rsr/views_partner_sites/base.py +++ b/akvo/rsr/views_partner_sites/base.py @@ -148,9 +148,21 @@ def render_to_response(self, context): return super(BaseProjectListView, self).render_to_response(context) def get_queryset(self): - projects = get_object_or_404( - Organisation, pk=self.request.organisation_id - ).published_projects().latest_update_fields().order_by('-id') + """here we check if the partner site should only show the projects belonging to the organisation and whether + it should filter based on specified keywords + """ + partner_site = self.request.partner_site + + # Check if only projects of the partner should be shown or all projects + if partner_site.partner_projects: + projects = get_object_or_404(Organisation, pk=self.request.organisation_id).published_projects() + else: + projects = Project.objects.all().published() + + # Check if keywords have been specified for the partner site and filter projects based on keywords if so + if partner_site.keywords.all(): + projects = projects.filter(keywords__in=partner_site.keywords.all()) + return ProjectFilterSet( self.request.GET.copy() or None, queryset=projects, diff --git a/akvo/rsr/views_partner_sites/widgets.py b/akvo/rsr/views_partner_sites/widgets.py index 6bbf2331cf..5c74390e3e 100644 --- a/akvo/rsr/views_partner_sites/widgets.py +++ b/akvo/rsr/views_partner_sites/widgets.py @@ -89,13 +89,21 @@ class ProjectListView(BaseWidgetView): def get_context_data(self, **kwargs): context = super(ProjectListView, self).get_context_data(**kwargs) - order_by = self.request.GET.get('order_by', 'title') - organisation = ( - get_object_or_404(Organisation, pk=self.request.organisation_id)) + organisation = get_object_or_404(Organisation, pk=self.request.organisation_id) + + partner_site = self.request.partner_site + + # Check if only projects of the partner should be shown or all projects + if partner_site.partner_projects: + projects = organisation.published_projects() + else: + projects = Project.objects.all().published() + + # Check if keywords have been specified for the partner site and filter projects based on keywords if so + if partner_site.keywords.all(): + projects = projects.filter(keywords__in=partner_site.keywords.all()) - projects = organisation.published_projects(). \ - status_not_archived().status_not_cancelled() sql = ( 'SELECT MAX(created_at) ' 'FROM rsr_projectupdate ' @@ -125,8 +133,22 @@ def get_context_data(self, **kwargs): context['height'] = self.request.GET.get('height', '300') context['width'] = self.request.GET.get('width', '600') context['state'] = self.request.GET.get('state', 'dynamic') - context['organisation'] = ( - get_object_or_404(Organisation, pk=self.request.organisation_id)) + + organisation = get_object_or_404(Organisation, pk=self.request.organisation_id) + partner_site = self.request.partner_site + + # Check if only projects of the partner should be shown or all projects + if partner_site.partner_projects: + projects = organisation.published_projects() + else: + projects = Project.objects.all().published() + + # Check if keywords have been specified for the partner site and filter projects based on keywords if so + if partner_site.keywords.all(): + projects = projects.filter(keywords__in=partner_site.keywords.all()) + + context['organisation'] = organisation + context['projects'] = projects # To handle old free form coloring via the bgcolor query parameter # the new way should be to use the "style" parameter with diff --git a/akvo/templates/partner_sites/widgets/projects_map.html b/akvo/templates/partner_sites/widgets/projects_map.html index 436ba97425..9277d2bef7 100644 --- a/akvo/templates/partner_sites/widgets/projects_map.html +++ b/akvo/templates/partner_sites/widgets/projects_map.html @@ -30,7 +30,7 @@ -{% partnersite_widget_project_map organisation.id width height|add:-30 state %} +{% partnersite_widget_project_map projects width height|add:-30 state %}