Compare commits

..

31 Commits

Author SHA1 Message Date
stupidcomputer 48a70fb637 fix: use the new bill template on index 2024-07-30 20:10:38 -05:00
stupidcomputer fad0b668fa fix: make deployment promptless 2024-07-30 17:03:03 -05:00
stupidcomputer 90e01d921f fix: forgot to add the country name to component 2024-07-30 17:02:50 -05:00
stupidcomputer 4d5d3f50a1 feature: all listing of all items in conferences 2024-07-30 16:58:19 -05:00
stupidcomputer 36c558821d feature: refactor bill display into components 2024-07-30 16:48:41 -05:00
stupidcomputer d713f4106d fix: change wording on stats page 2024-07-29 17:54:59 -05:00
stupidcomputer dce8c1b88c feature: add a search feature 2024-07-27 23:49:06 -05:00
stupidcomputer 25881d4cb3 feature: add a Category model
- add a Category model that represents the CCE
  defined categories for various legislation
2024-07-24 13:13:07 -05:00
stupidcomputer c92fbf0890 fix: remake migrations 2024-07-24 01:38:24 -05:00
stupidcomputer 61f1981925 fix: add __init__.py to migrations to make it work 2024-07-24 01:37:33 -05:00
stupidcomputer bccfa280e1 fix: sync the database after migrating 2024-07-24 01:32:23 -05:00
stupidcomputer c35c96e16b cleanup: refactor by_group view 2024-07-24 01:03:19 -05:00
stupidcomputer 2a6e42da9c to squash 2024-07-24 00:13:30 -05:00
stupidcomputer 25415a099f maint: add two sample books again 2024-07-23 23:51:47 -05:00
stupidcomputer 601ffd44c8 maint: squash migrations into one 2024-07-23 23:51:37 -05:00
stupidcomputer 4931c8ac94 fix: ValueError on index page when no legislation 2024-07-23 23:04:45 -05:00
stupidcomputer 281473262a make topics a part of the system we've made 2024-06-30 22:01:55 -05:00
stupidcomputer bfbea9e855 add automatic generation of country and school listing 2024-06-30 21:56:33 -05:00
stupidcomputer 0a6ed6ffe2 generalize the view generation into a factory 2024-06-30 21:05:34 -05:00
stupidcomputer 37e6a03bb3 final doc commit 2024-06-30 00:32:21 -05:00
stupidcomputer 08d9cb1b77 change 2 (to squash) 2024-06-30 00:32:21 -05:00
stupidcomputer 06539e332d some part of writing resolutions 2024-06-30 00:32:21 -05:00
stupidcomputer ff92a221f1 misc 2024-06-29 23:58:20 -05:00
stupidcomputer 39e81d5727 Add country model 2024-06-29 23:41:33 -05:00
stupidcomputer de4575a2f0 add a School model 2024-06-29 23:19:39 -05:00
stupidcomputer 761b00eb49 add country and catagory fields; simplify serialization into legislation objects 2024-06-29 22:27:05 -05:00
stupidcomputer a1c722295b add an admonition to contribute 2024-06-29 20:54:04 -05:00
stupidcomputer 2b026aaeee add contrib franklincce.nix NixOS module 2024-06-29 20:49:38 -05:00
stupidcomputer e8e83fc5d4 add a specific exemption for the admin panel 2024-06-29 18:33:30 -05:00
stupidcomputer f578e219c5 move /kb path to /knowledge
there's this weird thing that happens with nginx
with paths that end in /kb and I don't know why.
this at least fixes the problem.
2024-06-29 18:29:27 -05:00
stupidcomputer a965eb1aed add a homepage thing 2024-06-29 18:15:33 -05:00
31 changed files with 541 additions and 188 deletions

View File

@ -1,5 +1,5 @@
prod: # execute this target on the production server in the nix-shell prod: # execute this target on the production server in the nix-shell
cd franklincce; python3 manage.py collectstatic cd franklincce; yes yes | python3 manage.py collectstatic
sh gen_kb.sh sh gen_kb.sh
sed "s|change_me|$(shell dd if=/dev/urandom bs=1024 count=1|base64)|g" .env.prod.orig > .env.prod sed "s|change_me|$(shell dd if=/dev/urandom bs=1024 count=1|base64)|g" .env.prod.orig > .env.prod
docker-compose -f docker-compose.prod.yml up -d --build docker-compose -f docker-compose.prod.yml up -d --build

BIN
archive/MUNB2023.pdf Normal file

Binary file not shown.

Binary file not shown.

15
contrib/franklincce.nix Normal file
View File

@ -0,0 +1,15 @@
{ lib, config, pkgs, ... }:
{
# nix expressions to configure the relevant things
# manual nix-shell -c "make clean && make" (etc) is still needed, sadly
virtualisation.docker.enable = true;
services.nginx.virtualHosts."franklincce.beepboop.systems" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:1337";
};
};
}

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
python3 manage.py makemigrations python3 manage.py makemigrations
python3 manage.py migrate python3 manage.py migrate --run-syncdb
exec "$@" exec "$@"

View File

@ -11,7 +11,11 @@ class LegislationBookAdmin(admin.ModelAdmin):
to_register = [ to_register = [
[models.LegislativeText, LegislativeTextAdmin], [models.LegislativeText, LegislativeTextAdmin],
[models.LegislationBook, LegislationBookAdmin], [models.LegislationBook, LegislationBookAdmin],
[models.LegislationClassification] [models.LegislationClassification],
[models.School],
[models.Country],
[models.Sponsor],
[models.Category],
] ]
for i in to_register: for i in to_register:
admin.site.register(*i) admin.site.register(*i)

View File

@ -163,45 +163,63 @@ class CCEParserBase():
for legislative_text in splitted: for legislative_text in splitted:
# there are some blocks that contain just one value # there are some blocks that contain just one value
# and are aligned to some x value on the pdf # and are aligned to some x value on the pdf
# it's an easy way to extract stuff # it's an easy way to extract stuff
try:
country = get_block_by_x_value(legislative_text, 139).text.rstrip()
country = country.replace("Sponsor: ", "").lstrip()
except AttributeError:
country = None # this is a yig bill
try:
category = get_block_by_x_value(legislative_text, 151).text.rstrip().lstrip()
except AttributeError:
try:
category = get_block_by_x_value(legislative_text, 153).text.rstrip().lstrip()
except AttributeError:
print([(i.text, i.x0) for i in legislative_text])
leg_code = get_block_by_x_value(legislative_text, 88).text.rstrip() leg_code = get_block_by_x_value(legislative_text, 88).text.rstrip()
try: try:
school = get_block_by_x_value(legislative_text, 177).text.rstrip() school = get_block_by_x_value(legislative_text, 177).text.rstrip().lstrip()
except AttributeError: except AttributeError:
try: try:
school = get_block_by_x_value(legislative_text, 186).text.rstrip() school = get_block_by_x_value(legislative_text, 186).text.rstrip().lstrip()
except AttributeError: except AttributeError:
school = "you tell me, man" school = "you tell me, man"
try: try:
sponsors = get_block_by_x_value(legislative_text, 163).text.rstrip() sponsors = get_block_by_x_value(legislative_text, 163).text.rstrip().lstrip()
except AttributeError: except AttributeError:
try: try:
sponsors = get_block_by_x_value(legislative_text, 166).text.rstrip() sponsors = get_block_by_x_value(legislative_text, 166).text.rstrip().lstrip()
except AttributeError: except AttributeError:
sponsors = "you tell me, man" sponsors = "you tell me, man"
try:
subcommittee = get_block_by_x_value(legislative_text, 151).text.rstrip()
except AttributeError:
try:
subcommittee = get_block_by_x_value(legislative_text, 153).text.rstrip()
except AttributeError:
subcommittee = "you tell me, man"
the_rest = ''.join([i.text for i in legislative_text[12:]]) the_rest = ''.join([i.text for i in legislative_text[12:]])
print([i.text for i in legislative_text[12:]])
handled = self.handle_the_rest(the_rest) handled = self.handle_the_rest(the_rest)
title = handled["title"] title = handled["title"]
bill_text = handled["bill_text"] bill_text = handled["bill_text"]
codesplit = leg_code.split('/')
assembly = codesplit[0]
dashsplit = codesplit[1].split('-')
year = 2000 + int(dashsplit[0])
committee = int(dashsplit[1])
docket_order = int(dashsplit[2])
output.append({ output.append({
"code": leg_code, "assembly": assembly,
"year": year,
"committee": committee,
"docket_order": docket_order,
"category": category,
"country": country,
"school": school, "school": school,
"sponsors": sponsors, "sponsors": sponsors,
"subcommittee": subcommittee, "legislation_title": title,
"title": title, "text": bill_text
"bill_text": bill_text
}) })
self.output = output self.output = output
@ -255,22 +273,33 @@ class HSYIG24(CCEParserBase):
# it's an easy way to extract stuff # it's an easy way to extract stuff
legislative_text = remove_block_by_x_value(legislative_text, 565) # remove page numbers legislative_text = remove_block_by_x_value(legislative_text, 565) # remove page numbers
category = get_block_by_x_value(legislative_text, 139).text.rstrip().lstrip()
leg_code = get_block_by_x_value(legislative_text, 88).text.rstrip() leg_code = get_block_by_x_value(legislative_text, 88).text.rstrip()
school = get_block_by_x_value(legislative_text, 163).text.rstrip() school = get_block_by_x_value(legislative_text, 163).text.rstrip().lstrip()
sponsors = get_block_by_x_value(legislative_text, 152).text.rstrip() sponsors = get_block_by_x_value(legislative_text, 152).text.rstrip().lstrip()
subcommittee = get_block_by_x_value(legislative_text, 139).text.rstrip()
the_rest = ''.join([i.text for i in legislative_text[6:]]) the_rest = ''.join([i.text for i in legislative_text[6:]])
handled = self.handle_the_rest(the_rest) handled = self.handle_the_rest(the_rest)
title = handled["title"] title = handled["title"]
bill_text = handled["bill_text"] bill_text = handled["bill_text"]
codesplit = leg_code.split('/')
assembly = codesplit[0]
dashsplit = codesplit[1].split('-')
year = 2000 + int(dashsplit[0])
committee = int(dashsplit[1])
docket_order = int(dashsplit[2])
output.append({ output.append({
"code": leg_code, "assembly": assembly,
"year": year,
"committee": committee,
"docket_order": docket_order,
"category": category,
"country": None, # this is a yig bill
"school": school, "school": school,
"sponsors": sponsors, "sponsors": sponsors,
"subcommittee": subcommittee, "legislation_title": title,
"title": title, "text": bill_text
"bill_text": bill_text
}) })
self.output = output self.output = output
@ -287,8 +316,9 @@ def main():
return return
for text in doc.output: for text in doc.output:
print("{} ---------------------------- {}".format( print("{} {} {} ---------------------------- {}".format(
text["title"], text["bill_text"] text["country"], text["category"],
text["legislation_title"], text["text"]
)) ))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.12 on 2024-06-19 06:53 # Generated by Django 4.2.14 on 2024-07-24 06:38
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -12,6 +12,16 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Country',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256)),
],
options={
'verbose_name_plural': 'Countries',
},
),
migrations.CreateModel( migrations.CreateModel(
name='LegislationBook', name='LegislationBook',
fields=[ fields=[
@ -19,7 +29,38 @@ class Migration(migrations.Migration):
('conference_type', models.CharField(choices=[('M', 'Middle School'), ('H', 'High School')], default='H', max_length=1)), ('conference_type', models.CharField(choices=[('M', 'Middle School'), ('H', 'High School')], default='H', max_length=1)),
('pdf', models.FileField(upload_to='uploads/')), ('pdf', models.FileField(upload_to='uploads/')),
('name', models.CharField(max_length=256)), ('name', models.CharField(max_length=256)),
('import_strategy', models.CharField(max_length=128)), ('import_strategy', models.CharField(choices=[('HSYIGBookParser', 'High School YIG Book Parser 1'), ('HSMUNBookParser', 'High School MUN Book Parser 1')], default='HSYIGBookParser', max_length=128)),
('has_performed_export', models.BooleanField(default=False)),
],
options={
'verbose_name': 'Book',
'verbose_name_plural': 'Books',
},
),
migrations.CreateModel(
name='LegislationClassification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Name of this classification.', max_length=256)),
('text_to_match', models.CharField(help_text='a comma seperated list of keywords to include in the classification. spaces and dashes are discluded.', max_length=256)),
],
options={
'verbose_name': 'Topic',
'verbose_name_plural': 'Topics',
},
),
migrations.CreateModel(
name='School',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256)),
],
),
migrations.CreateModel(
name='Sponsor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -30,10 +71,17 @@ class Migration(migrations.Migration):
('text', models.TextField()), ('text', models.TextField()),
('year', models.IntegerField()), ('year', models.IntegerField()),
('committee', models.IntegerField()), ('committee', models.IntegerField()),
('category', models.CharField(max_length=256)),
('docket_order', models.IntegerField()), ('docket_order', models.IntegerField()),
('school', models.CharField(max_length=256)), ('legislation_title', models.CharField(max_length=512)),
('sponsors', models.CharField(max_length=256)), ('country', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='explorer.country')),
('from_book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='explorer.legislationbook')), ('from_book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='explorer.legislationbook')),
('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='explorer.school')),
('sponsors', models.ManyToManyField(blank=True, to='explorer.sponsor')),
], ],
options={
'verbose_name': 'Legislation',
'verbose_name_plural': 'Legislation',
},
), ),
] ]

View File

@ -0,0 +1,26 @@
# Generated by Django 4.2.14 on 2024-07-24 17:58
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('explorer', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256)),
],
),
migrations.AlterField(
model_name='legislativetext',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='explorer.category'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 4.2.12 on 2024-06-19 07:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('explorer', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='legislativetext',
name='legislation_title',
field=models.CharField(default='Sample title', max_length=512),
preserve_default=False,
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 4.2.12 on 2024-06-19 17:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('explorer', '0002_legislativetext_legislation_title'),
]
operations = [
migrations.AddField(
model_name='legislationbook',
name='has_performed_export',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='legislationbook',
name='import_strategy',
field=models.CharField(choices=[('HSYIGBookParser', 'High School YIG Book Parser 1'), ('HSMUNBookParser', 'High School MUN Book Parser 1')], default='HSYIGBookParser', max_length=128),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 4.2.12 on 2024-06-28 20:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('explorer', '0003_legislationbook_has_performed_export_and_more'),
]
operations = [
migrations.CreateModel(
name='LegislationClassification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Name of this classification.', max_length=256)),
('text_to_match', models.CharField(help_text='a comma seperated list of keywords to include in the classification. spaces and dashes are discluded.', max_length=256)),
],
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 4.2.12 on 2024-06-28 21:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('explorer', '0004_legislationclassification'),
]
operations = [
migrations.AddField(
model_name='legislationclassification',
name='obvious_change',
field=models.CharField(default='test', help_text='Name of this classification.', max_length=256),
preserve_default=False,
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 4.2.12 on 2024-06-28 21:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('explorer', '0005_legislationclassification_obvious_change'),
]
operations = [
migrations.RemoveField(
model_name='legislationclassification',
name='obvious_change',
),
]

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from .leglib import HSYIG24, HSMUN23 from .leglib import HSYIG24, HSMUN23
import io import io
@ -7,7 +8,70 @@ import fitz
from collections import namedtuple from collections import namedtuple
def InstantiateIfNone(model, name):
"""
Search the model for instances by name.
If there's none, then create one.
"""
filtered = model.objects.filter(name__exact=name)
try:
return filtered[0]
except IndexError:
obj = model(name=name)
obj.save()
return obj
class School(models.Model):
name = models.CharField(max_length=256)
def __str__(self):
return self.name
def get_absolute_url(self):
our_name = __class__.__name__
return reverse("{}.detail".format(our_name), kwargs={"model_id": self.id})
class Country(models.Model):
name = models.CharField(max_length=256)
class Meta:
verbose_name_plural = "Countries"
def __str__(self):
return self.name
def get_absolute_url(self):
our_name = __class__.__name__
return reverse("{}.detail".format(our_name), kwargs={"model_id": self.id})
class Sponsor(models.Model):
name = models.CharField(max_length=256)
def __str__(self):
return self.name
def get_absolute_url(self):
our_name = __class__.__name__
return reverse("{}.detail".format(our_name), kwargs={"model_id": self.id})
class Category(models.Model):
name = models.CharField(max_length=256)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return "{}".format(self.name)
def get_absolute_url(self):
our_name = __class__.__name__
return reverse("{}.detail".format(our_name), kwargs={"model_id": self.id})
class LegislationBook(models.Model): class LegislationBook(models.Model):
class Meta:
verbose_name = "Book"
verbose_name_plural = "Books"
class ConferenceType(models.TextChoices): class ConferenceType(models.TextChoices):
MIDDLE = "M", _("Middle School") MIDDLE = "M", _("Middle School")
HIGH = "H", _("High School") HIGH = "H", _("High School")
@ -47,30 +111,40 @@ class LegislationBook(models.Model):
return return
for text in parsed.output: for text in parsed.output:
print(text["code"]) text["school"] = InstantiateIfNone(School, text["school"])
codesplit = text["code"].split('/') if text["country"]:
assembly = codesplit[0] # there's sometimes "Dominican Republic" and "Dominican Republic 2"
dashsplit = codesplit[1].split('-') # handle that gracefully
year = 2000 + int(dashsplit[0]) text["country"] = text["country"].replace(" 2", "")
committee = int(dashsplit[1]) text["country"] = InstantiateIfNone(Country, text["country"])
docket_order = int(dashsplit[2])
text = LegislativeText( if not text["category"] or text["category"] == "Select One--":
assembly=assembly, text["category"] = "No category"
year=year, text["category"] = InstantiateIfNone(Category, text["category"])
committee=committee,
docket_order=docket_order, sponsors = text["sponsors"].split(', ')
school=text["school"], sponsors = [InstantiateIfNone(Sponsor, sponsor) for sponsor in sponsors]
sponsors=text["sponsors"],
legislation_title=text["title"], del text["sponsors"]
text=text["bill_text"],
from_book=self text = LegislativeText(**text, from_book=self)
)
text.save() text.save()
for sponsor in sponsors:
text.sponsors.add(sponsor)
def __str__(self): def __str__(self):
return "{}".format(self.name) return "{}".format(self.name)
def get_absolute_url(self):
our_name = __class__.__name__
return reverse("{}.detail".format(our_name), kwargs={"model_id": self.id})
class LegislativeText(models.Model): class LegislativeText(models.Model):
class Meta:
verbose_name = "Legislation"
verbose_name_plural = "Legislation"
class Assemblies(models.TextChoices): class Assemblies(models.TextChoices):
RGA = "RGA", _("Red General Assembly") RGA = "RGA", _("Red General Assembly")
BGA = "BGA", _("Blue General Assembly") BGA = "BGA", _("Blue General Assembly")
@ -93,16 +167,13 @@ class LegislativeText(models.Model):
text = models.TextField() text = models.TextField()
year = models.IntegerField() year = models.IntegerField()
committee = models.IntegerField() committee = models.IntegerField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
docket_order = models.IntegerField() docket_order = models.IntegerField()
school = models.CharField(max_length=256) school = models.ForeignKey(School, on_delete=models.CASCADE)
sponsors = models.CharField(max_length=256) sponsors = models.ManyToManyField(Sponsor, blank=True)
from_book = models.ForeignKey(LegislationBook, on_delete=models.CASCADE) from_book = models.ForeignKey(LegislationBook, on_delete=models.CASCADE)
legislation_title = models.CharField(max_length=512) legislation_title = models.CharField(max_length=512)
country = models.CharField( country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True)
max_length=512,
null=True,
blank=True
)
def __str__(self): def __str__(self):
return "{}/{}-{}-{}".format( return "{}/{}-{}-{}".format(
@ -136,6 +207,10 @@ class LegislativeText(models.Model):
return False return False
class LegislationClassification(models.Model): class LegislationClassification(models.Model):
class Meta:
verbose_name = "Topic"
verbose_name_plural = "Topics"
name = models.CharField(max_length=256, help_text="Name of this classification.") name = models.CharField(max_length=256, help_text="Name of this classification.")
text_to_match = models.CharField( text_to_match = models.CharField(
max_length=256, max_length=256,
@ -144,3 +219,16 @@ class LegislationClassification(models.Model):
def __str__(self): def __str__(self):
return "{}".format(self.name) return "{}".format(self.name)
def get_absolute_url(self):
our_name = __class__.__name__
return reverse("{}.detail".format(our_name), kwargs={"model_id": self.id})
models_in_index = [
LegislationClassification,
School,
Country,
Sponsor,
Category,
LegislationBook
]

View File

@ -14,11 +14,11 @@
<nav id="navbar" class="boxed"> <nav id="navbar" class="boxed">
<div id="leftnav"> <div id="leftnav">
<a href="/explorer">explorer</a> <a href="/explorer">explorer</a>
<a href="/kb">knowledge</a> <a href="/knowledge">knowledge</a>
</div> </div>
<div id="rightnav"> <div id="rightnav">
<a href="/explorer/all">all</a> <a href="/explorer/all">all</a>
<a href="/explorer/topics">by topic</a> <a href="/explorer/groups">by group</a>
<a href="/explorer/search">search</a> <a href="/explorer/search">search</a>
<a href="/explorer/stats">stats</a> <a href="/explorer/stats">stats</a>
</div> </div>

View File

@ -0,0 +1,15 @@
{% extends "explorer/base.html" %}
{% block content %}
<link rel="stylesheet" type="text/css" href="/static/tn.css" />
<div class="boxed">
<h1>View legislation</h1>
<ul>
{% for name, url in listing.items %}
<li><a href="{{ url }}">By {{ name }}</a></li>
{% endfor %}
</ul>
</div>
{% endblock content %}

View File

@ -0,0 +1,38 @@
{% comment %}
This is a component -- I'm only using this because:
1. I'm too lazy to install django-components, and
2. This is the only time a component is necessary, so it doesn't make
any sense to take on an extra dependancy.
{% endcomment %}
<div class="legcomponent">
<a href="/explorer/legislation/{{ legislation.id }}"><h2 class="legtitle">{{ legislation.legislation_title }}</h2></a>
<div class="legmetadata">
<p>
<i>{{ legislation.assembly }}/{{ legislation.committee }}/{{ legislation.docket_order }}</i>
&middot;
{% for sponsor in legislation.sponsors.all %}
<a href="/explorer/sponsors/{{ sponsor.id }}">{{ sponsor.name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
&middot;
<a href="/explorer/schools/{{ legislation.school.id }}">{{ legislation.school }}</a>
&middot;
{% if legislation.country %}
<a href="/explorer/countries/{{ legislation.country.id }}">{{ legislation.country }}</a>
&middot;
{% endif %}
<a href="/explorer/categories/{{ legislation.category.id }}">{{ legislation.category }}</a>
&middot;
<a href="/explorer/conference/{{ legislation.from_book.id }}">{{ legislation.from_book.name }}</a>
</p>
</div>
</div>

View File

@ -9,14 +9,16 @@
<h2>Some randomly selected legislation</h2> <h2>Some randomly selected legislation</h2>
{% if legislative_texts %} {% if legislation %}
{% autoescape off %}
<ul> <ul>
{% for text in legislative_texts %} {% for text in legislation %}
<li><a href="{% url 'viewleg' text.id %}">{{ text.legislation_title }}</a></li> {{ text }}
{% endfor %} {% endfor %}
</ul> </ul>
{% endautoescape %}
{% else %} {% else %}
<p>No texts available</p> <p>No texts available. If you're the admin, you need to add some texts with the admin interface.</p>
{% endif %} {% endif %}
<h2>About this instance</h2> <h2>About this instance</h2>

View File

@ -13,7 +13,18 @@
<p><i>{{ legislation.assembly }}/{{ legislation.committee }}/{{ legislation.docket_order }}</i></p> <p><i>{{ legislation.assembly }}/{{ legislation.committee }}/{{ legislation.docket_order }}</i></p>
<p>Sponsored by {{ legislation.sponsors }} of {{ legislation.school }}</p> <p>Sponsored by
{% for sponsor in legislation.sponsors.all %}
<a href="/explorer/sponsors/{{ sponsor.id }}">{{ sponsor.name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
of <a href="/explorer/schools/{{ legislation.school.id }}">{{ legislation.school }}</a></p>
{% if legislation.country %}
<p>The delegates above represented the <a href="/explorer/countries/{{ legislation.country.id }}">Delegation of {{ legislation.country }}</a>.</p>
{% endif %}
<p>This legislation was filed in the <a href="/explorer/categories/{{ legislation.category.id }}">{{ legislation.category }}</a> category</p>
<p>Presented as part of the <a href="/explorer/conference/{{ legislation.from_book.id }}">{{ legislation.from_book.name }}</a> conference</p> <p>Presented as part of the <a href="/explorer/conference/{{ legislation.from_book.id }}">{{ legislation.from_book.name }}</a> conference</p>
</div> </div>

View File

@ -0,0 +1,15 @@
{% extends "explorer/base.html" %}
{% block content %}
<link rel="stylesheet" type="text/css" href="/static/tn.css" />
<div class="boxed">
<h1>{{ result_name }}</h1>
<ul>
{% for instance in instances %}
<li><a href="{{ instance.get_absolute_url }}">{{ instance.name }}</a></li>
{% endfor %}
</ul>
</div>
{% endblock content %}

View File

@ -6,10 +6,12 @@
<div class="boxed"> <div class="boxed">
<h1>{{ result_name }}</h1> <h1>{{ result_name }}</h1>
{% autoescape off %}
<ul> <ul>
{% for text in legislation %} {% for text in legislation %}
<li><a href="/explorer/legislation/{{ text.id }}">{{ text.legislation_title }}</a></li> {{ text }}
{% endfor %} {% endfor %}
</ul> </ul>
{% endautoescape %}
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -0,0 +1,15 @@
{% extends "explorer/base.html" %}
{% block content %}
<link rel="stylesheet" type="text/css" href="/static/tn.css" />
<div class="boxed">
<h1>Search legislation by keyword</h1>
<form action="/explorer/search" method="get">
<input type="text" id="search_term" name="search_term" />
<button type="submit">Execute search</button>
<p>
You can use this page to search through legislation by keyword. The search is case sensitive, so be careful when pressing your shift key.
</p>
</form>
</div>
{% endblock %}

View File

@ -5,7 +5,7 @@
<div class="boxed"> <div class="boxed">
<h1>Explorer statistics</h1> <h1>Explorer statistics</h1>
<p>Total bills: {{ all }}</p> <p>Total pieces of legislation: {{ all }}</p>
<p>Red Senate Bills: {{ red_senate }}</p> <p>Red Senate Bills: {{ red_senate }}</p>
@ -19,10 +19,10 @@
<p>Blue House Bills: {{ blue_house }}</p> <p>Blue House Bills: {{ blue_house }}</p>
<p>Red General Assembly Bills: {{ red_ga }}</p> <p>Red General Assembly Resolutions: {{ red_ga }}</p>
<p>White General Assembly Bills: {{ white_ga }}</p> <p>White General Assembly Resolutions: {{ white_ga }}</p>
<p>Blue General Assembly Bills: {{ blue_ga }}</p> <p>Blue General Assembly Resolutions: {{ blue_ga }}</p>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -7,7 +7,20 @@ urlpatterns = [
path("all/", views.all, name="all"), path("all/", views.all, name="all"),
path("stats/", views.stats, name="stats"), path("stats/", views.stats, name="stats"),
path("legislation/<int:legislation_id>/", views.view_legislation, name="viewleg"), path("legislation/<int:legislation_id>/", views.view_legislation, name="viewleg"),
path("conference/<int:conference_id>/", views.view_conference, name="viewconf"), path("topics/<int:model_id>/", views.get_all_classified_by_id, name="LegislationClassification.detail"),
path("topics/<int:classification_id>/", views.get_all_classified_by_id, name="classificationview"), path("topics/", views.get_all_classifications, name="LegislationClassification"),
path("topics/", views.get_all_classifications, name="classificationview"), path("search/", views.handle_search, name="search_legislation"),
# these are named weirdly -- see models.py School and Country definitions
path("schools/<int:model_id>/", views.get_all_by_school, name="School.detail"),
path("countries/<int:model_id>/", views.get_all_by_country, name="Country.detail"),
path("sponsors/<int:model_id>/", views.get_all_by_sponsor, name="Sponsor.detail"),
path("categories/<int:model_id>/", views.get_all_by_category, name="Category.detail"),
path("conference/<int:model_id>/", views.get_all_by_conference, name="LegislationBook.detail"),
path("schools/", views.get_all_schools, name="School"),
path("countries/", views.get_all_countries, name="Country"),
path("groups/", views.return_groups, name="Groups"),
path("sponsors/", views.get_all_sponsors, name="Sponsor"),
path("categories/", views.get_all_categories, name="Category"),
path("conference/", views.get_all_conferences, name="LegislationBook"),
] ]

View File

@ -1,15 +1,40 @@
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
from django.urls import reverse
from django.http import HttpResponse from django.http import HttpResponse
from .models import LegislativeText, LegislationBook, LegislationClassification from .models import (
LegislativeText,
LegislationBook,
LegislationClassification,
School,
Country,
Sponsor,
Category,
models_in_index,
)
from random import sample from random import sample
def legislation_to_html(legislation):
return render_to_string("explorer/comp_legislation.html", {
"legislation": legislation,
})
def index(request): def index(request):
legislative_texts = list(LegislativeText.objects.all()) legislative_texts = list(LegislativeText.objects.all())
legislative_texts = sample(legislative_texts, 5) try:
legislative_texts = sample(legislative_texts, 5)
except ValueError:
# there's not enough texts, so just return nothing
legislative_texts = []
legislative_texts = [
legislation_to_html(text) for text in legislative_texts
]
context = { context = {
"legislative_texts": legislative_texts, "legislation": legislative_texts,
} }
return render(request, "explorer/index.html", context) return render(request, "explorer/index.html", context)
@ -54,14 +79,8 @@ def stats(request):
} }
return render(request, "explorer/stats.html", context) return render(request, "explorer/stats.html", context)
def get_all_classifications(request): def get_all_classified_by_id(request, model_id):
classifications = LegislationClassification.objects.all() classification = get_object_or_404(LegislationClassification, pk=model_id)
return render(request, "explorer/classifications.html", {
"classifications": classifications,
})
def get_all_classified_by_id(request, classification_id):
classification = get_object_or_404(LegislationClassification, pk=classification_id)
# this is very expensive; make a way for this to be cached please? # this is very expensive; make a way for this to be cached please?
all_texts = LegislativeText.objects.all() all_texts = LegislativeText.objects.all()
@ -80,3 +99,86 @@ def get_all_classified_by_id(request, classification_id):
"legislation": matches, "legislation": matches,
"result_name": "All legislation in topic {}".format(classification.name) "result_name": "All legislation in topic {}".format(classification.name)
}) })
def get_all_by_x(model):
def wrapped(request, model_id):
instance = get_object_or_404(model, pk=model_id)
legislation = instance.legislativetext_set.all()
legislation = [legislation_to_html(i) for i in legislation]
return render(request, "explorer/results.html", {
"result_name": "All legislation by {}".format(instance.name),
"legislation": legislation
})
return wrapped
def get_all_xs(model):
def wrapper(request):
instances = model.objects.all()
try:
# what the heck, django?????
plural = model._meta.verbose_name_plural
except:
plural = model.__name__ + "s"
plural = plural.lower()
return render(request, "explorer/listing.html", {
"result_name": "All {}".format(plural),
"instances": instances,
})
return wrapper
def return_groups(request):
listing = {}
for model in models_in_index:
try:
name = model._meta.verbose_name.lower()
except:
name = model.__name__.lower()
listing[name] = reverse(model.__name__)
print(listing)
return render(request, "explorer/by_group.html", { "listing": listing })
def handle_search(request):
try:
query = request.GET['search_term']
except KeyError:
return render(request, "explorer/search.html", {})
f = LegislativeText.objects.filter
text_results = f(text__icontains=query)
title_results = f(legislation_title__icontains=query)
school_results = f(school__name__icontains=query)
sponsor_results = f(sponsors__name__icontains=query)
country_results = f(country__name__icontains=query)
results = text_results.union(
title_results,
school_results,
sponsor_results,
country_results
)
results = [legislation_to_html(i) for i in results]
return render(request, "explorer/results.html", {
"result_name": "Results for search term '{}'".format(query),
"legislation": results
})
get_all_by_school = get_all_by_x(School)
get_all_by_country = get_all_by_x(Country)
get_all_by_sponsor = get_all_by_x(Sponsor)
get_all_by_category = get_all_by_x(Category)
get_all_by_conference = get_all_by_x(LegislationBook)
get_all_schools = get_all_xs(School)
get_all_countries = get_all_xs(Country)
get_all_sponsors = get_all_xs(Sponsor)
get_all_categories = get_all_xs(Category)
get_all_classifications = get_all_xs(LegislationClassification)
get_all_conferences = get_all_xs(LegislationBook)

View File

@ -2,9 +2,12 @@ files=$(find kb -type f | awk -F'/' '{print $NF}')
IFS=' IFS='
' '
mkdir -p franklincce/staticfiles/kb mkdir -p franklincce/staticfiles/kb
mkdir -p franklincce/staticfiles/root
for file in $files; do for file in $files; do
without_extension=${file%.*} without_extension=${file%.*}
echo $file, $without_extension echo $file, $without_extension
pandoc -s --template=./template.html -f markdown -t html -o "franklincce/staticfiles/kb/$without_extension.html" "kb/$without_extension.md" --lua-filter=links-to-html.lua pandoc -s --template=./template.html -f markdown -t html -o "franklincce/staticfiles/kb/$without_extension.html" "kb/$without_extension.md" --lua-filter=links-to-html.lua
done done
cp franklincce/staticfiles/kb/web_root.html franklincce/staticfiles/root/index.html

View File

@ -5,3 +5,6 @@ title: "Franklin CCE Knowledgebase"
## Model UN ## Model UN
- [Writing a resolution](./writing-resolution.md) - [Writing a resolution](./writing-resolution.md)
Want to see more articles in the knowledgebase?
[Learn how to contribute](./CONTRIBUTING.md).

6
kb/web_root.md Normal file
View File

@ -0,0 +1,6 @@
---
title: "Franklin CCE Delegation website"
---
This website contains some materials for the Franklin High School YMCA Center for Civic Engagement delegation.
Specifically, a [bill database](/explorer) and a [collection of helpful articles](/knowledge) on various things are available here.

View File

@ -4,8 +4,20 @@ upstream franklincce {
server { server {
listen 80; listen 80;
server_name franklincce.beepboop.systems;
port_in_redirect off;
absolute_redirect off;
server_name_in_redirect off;
location / { location /explorer/ {
proxy_pass http://franklincce;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
client_max_body_size 100M;
}
location /admin/ {
proxy_pass http://franklincce; proxy_pass http://franklincce;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host; proxy_set_header Host $host;
@ -17,7 +29,11 @@ server {
alias /home/app/web/staticfiles/; alias /home/app/web/staticfiles/;
} }
location /kb { location /knowledge {
alias /home/app/web/staticfiles/kb; alias /home/app/web/staticfiles/kb;
} }
location / {
alias /home/app/web/staticfiles/root/;
}
} }

View File

@ -14,7 +14,7 @@
<nav id="navbar" class="boxed"> <nav id="navbar" class="boxed">
<div id="leftnav"> <div id="leftnav">
<a href="/explorer">explorer</a> <a href="/explorer">explorer</a>
<a href="/kb">knowledge</a> <a href="/knowledge">knowledge</a>
</div> </div>
</nav> </nav>