-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #392 from datamade/minimal
big merge in of django-councilmatic views and functionality
- Loading branch information
Showing
131 changed files
with
1,656 additions
and
875 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,278 @@ | ||
from councilmatic_core.feeds import CouncilmaticFacetedSearchFeed, BillDetailActionFeed | ||
from chicago.models import ChicagoBill | ||
import urllib | ||
|
||
from haystack.query import SearchQuerySet | ||
|
||
class ChicagoCouncilmaticFacetedSearchFeed(CouncilmaticFacetedSearchFeed): | ||
# same as CouncilmaticFacetedSearchFeed but have a better item name | ||
# template which uses NYCBill's friendly_name() as opposed to Bill's | ||
# friendly_name() | ||
title_template = "feeds/chicago_search_item_title.html" | ||
bill_model = ChicagoBill | ||
from django.contrib.syndication.views import Feed | ||
from django.utils.feedgenerator import Rss201rev2Feed | ||
from django.urls import reverse, reverse_lazy | ||
from django.conf import settings | ||
|
||
from .models import Person, Bill, Organization, Event | ||
from .utils import to_datetime | ||
|
||
class ChicagoBillDetailActionFeed(BillDetailActionFeed): | ||
title_template = "feeds/chicago_bill_actions_item_title.html" | ||
|
||
class FacetedSearchFeed(Feed): | ||
title_template = "feeds/search_item_title.html" | ||
description_template = "feeds/search_item_description.html" | ||
bill_model = Bill | ||
|
||
all_results = None | ||
sqs = ( | ||
SearchQuerySet() | ||
.facet("bill_type") | ||
.facet("sponsorships", sort="index") | ||
.facet("controlling_body") | ||
.facet("inferred_status") | ||
) | ||
query = None | ||
|
||
def url_with_querystring(self, path, **kwargs): | ||
return path + "?" + urllib.parse.urlencode(kwargs) | ||
|
||
def get_object(self, request): | ||
self.queryDict = request.GET | ||
|
||
all_results = SearchQuerySet().all() | ||
facets = None | ||
|
||
if "selected_facets" in request.GET: | ||
facets = request.GET.getlist("selected_facets") | ||
|
||
if "q" in request.GET: | ||
self.query = request.GET["q"] | ||
results = all_results.filter(content=self.query) | ||
|
||
if facets: | ||
for facet in facets: | ||
(facet_name, facet_value) = facet.split(":") | ||
facet_name = facet_name.rsplit("_exact")[0] | ||
results = results.narrow("%s:%s" % (facet_name, facet_value)) | ||
elif facets: | ||
for facet in facets: | ||
(facet_name, facet_value) = facet.split(":") | ||
facet_name = facet_name.rsplit("_exact")[0] | ||
results = all_results.narrow("%s:%s" % (facet_name, facet_value)) | ||
|
||
return results.order_by("-last_action_date") | ||
|
||
def title(self, obj): | ||
if self.query: | ||
title = ( | ||
settings.SITE_META["site_name"] | ||
+ ": Search for '" | ||
+ self.query.capitalize() | ||
+ "'" | ||
) | ||
# XXX: create a nice title based on all search parameters | ||
else: | ||
title = settings.SITE_META["site_name"] + ": Filtered Search" | ||
|
||
return title | ||
|
||
def link(self, obj): | ||
# return the main non-RSS search URL somehow | ||
# XXX maybe "quargs" - evz | ||
# return reverse('councilmatic_search', args=(searchqueryset=self.sqs,)) | ||
url = self.url_with_querystring( | ||
reverse("{}:councilmatic_search_feed".format(settings.APP_NAME)), | ||
q=self.query, | ||
) | ||
return url | ||
|
||
def item_link(self, bill): | ||
return reverse("bill_detail", args=(bill.slug,)) | ||
|
||
def item_pubdate(self, bill): | ||
return to_datetime(bill.last_action_date) | ||
|
||
def description(self, obj): | ||
return "Bills returned from search" | ||
|
||
def items(self, query): | ||
l_items = query[:20] | ||
pks = [i.pk for i in l_items] | ||
bills = self.bill_model.objects.filter(pk__in=pks).order_by("-last_action_date") | ||
return bills | ||
|
||
|
||
class PersonDetailFeed(Feed): | ||
"""The PersonDetailFeed provides an RSS feed for a given committee member, | ||
returning the most recent 20 bills for which they are the primary sponsor; | ||
and for each bill, the list of sponsores and the action history. | ||
""" | ||
|
||
title_template = "feeds/person_detail_item_title.html" | ||
description_template = "feeds/person_detail_item_description.html" | ||
feed_type = Rss201rev2Feed | ||
NUM_RECENT_BILLS = 20 | ||
|
||
def get_object(self, request, slug): | ||
o = Person.objects.get(slug=slug) | ||
return o | ||
|
||
def title(self, obj): | ||
return ( | ||
settings.SITE_META["site_name"] | ||
+ ": " | ||
+ settings.CITY_VOCAB["COUNCIL_MEMBER"] | ||
+ " %s: Recently Sponsored Bills" % obj.name | ||
) | ||
|
||
def link(self, obj): | ||
return reverse("person", args=(obj.slug,)) | ||
|
||
def item_link(self, bill): | ||
# return the Councilmatic URL for the bill | ||
return reverse("bill_detail", args=(bill.slug,)) | ||
|
||
def item_pubdate(self, bill): | ||
return to_datetime(bill.last_action_date) | ||
|
||
def description(self, obj): | ||
return "Recent sponsored bills from " + obj.name + "." | ||
|
||
def items(self, person): | ||
sponsored_bills = [s.bill for s in person.primary_sponsorships][:10] | ||
recent_sponsored_bills = sponsored_bills[: self.NUM_RECENT_BILLS] | ||
return recent_sponsored_bills | ||
|
||
|
||
class CommitteeDetailEventsFeed(Feed): | ||
"""The CommitteeDetailEventsFeed provides an RSS feed for a given committee, | ||
returning the most recent 20 events. | ||
""" | ||
|
||
title_template = "feeds/committee_events_item_title.html" | ||
description_template = "feeds/committee_events_item_description.html" | ||
feed_type = Rss201rev2Feed | ||
NUM_RECENT_COMMITTEE_EVENTS = 20 | ||
|
||
def get_object(self, request, slug): | ||
o = Organization.objects.get(slug=slug) | ||
return o | ||
|
||
def title(self, obj): | ||
return settings.SITE_META["site_name"] + ": " + obj.name + ": Recent Events" | ||
|
||
def link(self, obj): | ||
# return the Councilmatic URL for the committee | ||
return reverse("committee_detail", args=(obj.slug,)) | ||
|
||
def item_link(self, event): | ||
# return the Councilmatic URL for the event | ||
return reverse("event_detail", args=(event.slug,)) | ||
|
||
def item_pubdate(self, event): | ||
return event.start_time | ||
|
||
def description(self, obj): | ||
return "Events for committee %s" % obj.name | ||
|
||
def items(self, obj): | ||
return obj.recent_events.all()[: self.NUM_RECENT_COMMITTEE_EVENTS] | ||
|
||
|
||
class CommitteeDetailActionFeed(Feed): | ||
"""The CommitteeDetailActionFeed provides an RSS feed for a given committee, | ||
returning the most recent 20 actions on legislation. | ||
""" | ||
|
||
# instead of defining item_title() or item_description(), use templates | ||
title_template = "feeds/committee_actions_item_title.html" | ||
description_template = "feeds/committee_actions_item_description.html" | ||
feed_type = Rss201rev2Feed | ||
NUM_RECENT_COMMITTEE_ACTIONS = 20 | ||
|
||
def get_object(self, request, slug): | ||
o = Organization.objects.get(slug=slug) | ||
return o | ||
|
||
def title(self, obj): | ||
return settings.SITE_META["site_name"] + ": " + obj.name + ": Recent Actions" | ||
|
||
def link(self, obj): | ||
# return the Councilmatic URL for the committee | ||
return reverse("committee_detail", args=(obj.slug,)) | ||
|
||
def item_link(self, action): | ||
# return the Councilmatic URL for the bill | ||
return reverse("bill_detail", args=(action.bill.slug,)) | ||
|
||
def item_pubdate(self, action): | ||
return to_datetime(action.date_dt) | ||
|
||
def description(self, obj): | ||
return "Actions for committee %s" % obj.name | ||
|
||
def items(self, obj): | ||
return obj.recent_activity[: self.NUM_RECENT_COMMITTEE_ACTIONS] | ||
|
||
|
||
class BillDetailActionFeed(Feed): | ||
""" | ||
Return the last 20 actions for a given bill. | ||
""" | ||
|
||
# instead of defining item_title() or item_description(), use templates | ||
title_template = "feeds/bill_actions_item_title.html" | ||
description_template = "feeds/bill_actions_item_description.html" | ||
feed_type = Rss201rev2Feed | ||
NUM_RECENT_BILL_ACTIONS = 20 | ||
|
||
def get_object(self, request, slug): | ||
o = Bill.objects.get(slug=slug) | ||
return o | ||
|
||
def title(self, obj): | ||
return ( | ||
settings.SITE_META["site_name"] | ||
+ ": " | ||
+ obj.friendly_name | ||
+ ": Recent Actions" | ||
) | ||
|
||
def link(self, obj): | ||
# return the Councilmatic URL for the committee | ||
return reverse("bill_detail", args=(obj.slug,)) | ||
|
||
def item_link(self, action): | ||
# Bill actions don't have their own pages, so just link to the Bill page (?) | ||
return reverse("bill_detail", args=(action.bill.slug,)) | ||
|
||
def item_pubdate(self, action): | ||
return to_datetime(action.date_dt) | ||
|
||
def description(self, obj): | ||
return "Actions for bill %s" % obj.friendly_name | ||
|
||
def items(self, obj): | ||
return obj.ordered_actions[: self.NUM_RECENT_BILL_ACTIONS] | ||
|
||
|
||
class EventsFeed(Feed): | ||
""" | ||
Return the last 20 announced events as per, e.g., | ||
https://nyc.councilmatic.org/events/ | ||
""" | ||
|
||
title_template = "feeds/events_item_title.html" | ||
description_template = "feeds/events_item_description.html" | ||
feed_type = Rss201rev2Feed | ||
NUM_RECENT_EVENTS = 20 | ||
|
||
title = settings.CITY_COUNCIL_NAME + " " + "Recent Events" | ||
link = reverse_lazy("events") | ||
description = "Recently announced events." | ||
|
||
def item_link(self, event): | ||
# return the Councilmatic URL for the event | ||
return reverse("event_detail", args=(event.slug,)) | ||
|
||
def item_pubdate(self, event): | ||
return event.start_time | ||
|
||
def description(self, obj): | ||
return "Events" | ||
|
||
def items(self, obj): | ||
return Event.objects.all()[: self.NUM_RECENT_EVENTS] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import json | ||
|
||
from django.core.management.base import BaseCommand, CommandError | ||
from django.contrib.gis.geos import GEOSGeometry | ||
|
||
from councilmatic_core import models | ||
|
||
|
||
class Command(BaseCommand): | ||
help = "Import boundary shapefiles for Post entities" | ||
|
||
def add_arguments(self, parser): | ||
parser.add_argument( | ||
"geojson_file", | ||
help=( | ||
"The location of the GeoJSON file containing shapes for each " | ||
"Division, relative to the project root. The file should be " | ||
"formatted as a GeoJSON FeatureCollection where each Feature A) " | ||
"corresponds to a distinct Division and B) has a 'division_id' " | ||
"attribute in the 'properties' object. " | ||
), | ||
) | ||
|
||
def handle(self, *args, **options): | ||
self.stdout.write("Populating shapes for Posts...") | ||
shapes_populated = 0 | ||
|
||
with open(options["geojson_file"]) as shapef: | ||
shapes = json.load(shapef) | ||
|
||
features = self._get_or_raise( | ||
shapes, "features", 'Could not find the "features" array in the input file.' | ||
) | ||
|
||
for feature in features: | ||
shape = self._get_or_raise( | ||
feature, "geometry", 'Could not find a "geometry" key in the Feature.' | ||
) | ||
properties = self._get_or_raise( | ||
feature, | ||
"properties", | ||
'Could not find a "properties" key in the Feature.', | ||
) | ||
division_id = self._get_or_raise( | ||
properties, | ||
"division_id", | ||
'Could not find a "division_id" key in the Feature properties.', | ||
) | ||
|
||
models.Post.objects.filter(division_id=division_id).update( | ||
shape=GEOSGeometry(json.dumps(shape)) | ||
) | ||
shapes_populated += 1 | ||
|
||
self.stdout.write( | ||
self.style.SUCCESS("Populated {} shapes".format(str(shapes_populated))) | ||
) | ||
|
||
def _get_or_raise(self, dct, key, msg): | ||
""" | ||
Check to see if 'dct' has a key corresponding to 'key', and raise an | ||
error if it doesn't. | ||
""" | ||
format_prompt = ( | ||
"Is the input file formatted as a GeoJSON FeatureCollection " | ||
'where each feature has a "division_id" property?' | ||
) | ||
if not dct.get(key): | ||
raise CommandError(msg + " " + format_prompt) | ||
else: | ||
return dct[key] |
Oops, something went wrong.