,
33 | formFields: T
34 | ): string {
35 | return _.isFunction(validation.errorMessage)
36 | ? validation.errorMessage(formFields)
37 | : validation.errorMessage;
38 | }
39 | }
40 |
41 | export default validationHelper;
42 |
--------------------------------------------------------------------------------
/common/fixtures/testdata readme.txt:
--------------------------------------------------------------------------------
1 | -- User Accounts --
2 | dev-admin (Admin only)
3 | Password: code4Gud!
4 |
5 | marlonakeating+test@gmail.com
6 | Password: Qwer1234!
7 |
8 | marlonakeating+test2@gmail.com
9 | Password: Qwer1234!
10 |
11 | marlonakeating+test3@gmail.com
12 | Password: Qwer1234!
13 |
14 | marlonakeating+testadmin@gmail.com
15 | Password: Qwer1234!
16 |
17 | Other volunteer accounts (Password: Qwer1234!)
18 | marlonakeating+techdirector@gmail.com
19 | marlonakeating+leadengineer@gmail.com
20 | marlonakeating+creativedirector@gmail.com
21 | marlonakeating+leaddesigner@gmail.com
22 | marlonakeating+productlead@gmail.com
23 | marlonakeating+marketinglead@gmail.com
24 | marlonakeating+operations@gmail.com
25 | marlonakeating+recruiting@gmail.com
26 | marlonakeating+projectmanager@gmail.com
27 | marlonakeating+fundraising@gmail.com
28 | marlonakeating+marketing@gmail.com
29 | marlonakeating+productmanager@gmail.com
30 | marlonakeating+uxdesign@gmail.com
31 | marlonakeating+dev@gmail.com
32 |
--------------------------------------------------------------------------------
/common/helpers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/common/helpers/__init__.py
--------------------------------------------------------------------------------
/common/helpers/date_helpers.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from enum import Enum
3 |
4 |
5 | class DateTimeFormats(Enum):
6 | UTC_DATETIME = '%Y-%m-%dT%H:%M:%SZ'
7 | DATE_LOCALIZED = '%x'
8 | DATEPICKER_DATE = '%a %b %d %Y %H:%M:%S GMT%z'
9 | MONTH_DD_YYYY = '%B %d, %Y'
10 | SCHEDULED_DATE_TIME = '[%A, %B %d, %Y] at [%H:%M:%S %Z]'
11 |
12 |
13 | def datetime_field_to_datetime(field):
14 | # For some reason django's DateTimeField's are sometimes rendered as plain strings, so we need to parse them back into datetimes
15 | if isinstance(field, str):
16 | return datetime.strptime(field, DateTimeFormats.UTC_DATETIME.value)
17 | else:
18 | return field
19 |
20 |
21 | def datetime_to_string(date_time, date_time_format):
22 | return date_time.strftime(date_time_format.value)
23 |
24 |
25 | def parse_front_end_datetime(date_str):
26 | # Example date string: Mon Feb 03 2020 18:30:00 GMT-0800 (Pacific Standard Time)
27 | # First chop off extraneous timezone name at the end
28 | pruned_date_str = date_str[0: date_str.index('(') - 1]
29 | # Parse according to format
30 | return datetime.strptime(pruned_date_str, DateTimeFormats.DATEPICKER_DATE.value)
31 |
--------------------------------------------------------------------------------
/common/helpers/db.py:
--------------------------------------------------------------------------------
1 | from django.db import connection
2 |
3 |
4 | def db_table_exists(table_name):
5 | return table_name in connection.introspection.table_names()
6 |
7 |
8 | # Check to see if democracylab's database tables have been initialized yet
9 | def db_is_initialized():
10 | return db_table_exists('common_tag')
11 |
12 |
13 | def bulk_delete(Table, delete_results):
14 | ids = delete_results.values_list("id", flat=True)
15 | Table.objects.filter(pk__in=list(ids)).delete()
16 |
17 |
18 | def unique_column_values(model, column, filter_func):
19 | values = map(lambda obj_property: obj_property[column], model.objects.values(column).distinct())
20 | return list(filter(filter_func, values))
--------------------------------------------------------------------------------
/common/helpers/dictionaries.py:
--------------------------------------------------------------------------------
1 | def merge_dicts(*dict_args):
2 | """
3 | Given any number of dicts, shallow copy and merge into a new dict,
4 | precedence goes to key value pairs in latter dicts.
5 | Taken from https://stackoverflow.com/a/26853961/6326903
6 | """
7 | result = {}
8 | for dictionary in dict_args:
9 | result.update(dictionary)
10 | return result
11 |
12 |
13 | def keys_subset(base_dict, keys):
14 | """
15 | Generate a dictionary that contains a subset of the entries of the base dictionary
16 | with the given keys
17 | """
18 | return dict((k, base_dict[k]) for k in keys if k in base_dict)
19 |
20 |
21 | def keys_omit(base_dict, keys):
22 | """
23 | Generate a dictionary that contains a subset of the entries of the base dictionary
24 | that do not match the given keys
25 | """
26 | all_keys = set(base_dict.keys())
27 | omitted_keys = set(keys)
28 | include_keys = all_keys - omitted_keys
29 | return keys_subset(base_dict, include_keys)
30 |
--------------------------------------------------------------------------------
/common/helpers/error_handlers.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from django.shortcuts import redirect
3 | from common.helpers.constants import FrontEndSection
4 | from common.helpers.dictionaries import merge_dicts
5 | from common.helpers.front_end import section_url
6 |
7 |
8 | class ReportableError(Exception):
9 | """Exception raised that needs to be logged and sent to a front end error page
10 |
11 | Attributes:
12 | message -- explanation of the error to be reported in the logs
13 | front_end_args -- arguments to be surfaced on the front end error page
14 | """
15 |
16 | def __init__(self, message, front_end_args):
17 | self.message = message
18 | self.front_end_args = front_end_args or {}
19 |
20 |
21 | def handle500(request):
22 | exception_type, exception, traceback = sys.exc_info()
23 | if isinstance(exception, ReportableError):
24 | # Log message
25 | print("Error(500): " + exception.message)
26 | error_args = merge_dicts(exception.front_end_args, {'errorType': type(exception).__name__})
27 | # Redirect to Error page
28 | return redirect(section_url(FrontEndSection.Error, error_args))
29 | else:
30 | return redirect(section_url(FrontEndSection.Error))
31 |
32 |
--------------------------------------------------------------------------------
/common/helpers/queue.py:
--------------------------------------------------------------------------------
1 | import os
2 | import redis
3 | import threading
4 | from rq import Queue
5 | from ssl import CERT_NONE
6 | from typing import Callable
7 | from django.conf import settings
8 | from urllib.parse import urlparse
9 |
10 |
11 | redis_url = os.getenv('REDIS_URL','redis://localhost:6379')
12 | url = urlparse(redis_url)
13 | # Check if the Redis connection is using SSL/TSL
14 | is_secure = redis_url.startswith('rediss://')
15 |
16 | if is_secure:
17 | conn = redis.Redis(host=url.hostname, port=url.port, username=url.username, password=url.password, ssl=True, ssl_cert_reqs=None)
18 | else:
19 | conn = redis.from_url(redis_url)
20 |
21 | q = settings.REDIS_ENABLED and Queue(connection=conn)
22 |
23 | def enqueue(job_func: Callable, *args):
24 | if settings.REDIS_ENABLED:
25 | job = q.enqueue(job_func, *args)
26 | from pprint import pprint
27 | pprint(job)
28 | return job
29 | else:
30 | # If redis is not enabled, use thread
31 | thread = threading.Thread(target=job_func, args=args)
32 | thread.daemon = True
33 | thread.start()
34 |
35 |
--------------------------------------------------------------------------------
/common/helpers/random.py:
--------------------------------------------------------------------------------
1 | import uuid
2 |
3 |
4 | def generate_uuid():
5 | return uuid.uuid4().hex
--------------------------------------------------------------------------------
/common/helpers/request_helpers.py:
--------------------------------------------------------------------------------
1 | from urllib import parse as urlparse
2 | from common.helpers.dictionaries import merge_dicts
3 |
4 |
5 | class ResourceNotFound(Exception):
6 | """Raised when a remote resource can't be found
7 |
8 | Attributes:
9 | url -- Url of resource request that failed
10 | """
11 |
12 | def __init__(self, url):
13 | self.url = url
14 |
15 |
16 | def url_params(request):
17 | from common.helpers.front_end import get_page_path_parameters
18 | path_args = get_page_path_parameters(request.path)
19 | url_args = request.GET.urlencode()
20 | return merge_dicts(path_args, urlparse.parse_qs(url_args, keep_blank_values=0, strict_parsing=0))
21 |
22 |
23 | def is_ajax(request):
24 | return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
25 |
--------------------------------------------------------------------------------
/common/helpers/retry.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 |
4 | def retry(func, retry_count, retry_seconds, job_name=None):
5 | result = None
6 | retries = 0
7 | while retries <= retry_count:
8 | try:
9 | result = func()
10 | break
11 | except:
12 | if retries == retry_count:
13 | raise
14 | retries = retries + 1
15 | if job_name is not None:
16 | print(f'{job_name} failed, retrying ({retries} of {retry_count})')
17 | time.sleep(retry_seconds)
18 | continue
19 | return result
20 |
--------------------------------------------------------------------------------
/common/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/common/management/commands/__init__.py
--------------------------------------------------------------------------------
/common/management/commands/activate_event.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 |
3 |
4 | class Command(BaseCommand):
5 | def add_arguments(self, parser):
6 | parser.add_argument(
7 | 'event_id'
8 | )
9 |
10 | def handle(self, *args, **options):
11 | from civictechprojects.models import Event, EventConferenceRoom
12 | event = Event.get_by_id_or_slug(options['event_id'])
13 | # Room ids are keyed to project ids
14 | event_projects = event.get_event_projects()
15 | room_ids = [0] + list(map(lambda ep: ep.project.id, event_projects))
16 | event_project_index = {ep.project.id: ep for ep in event_projects}
17 | # TODO: Batch these calls properly when bug is fixed on Qiqochat's side
18 | qiqo_event_id = event.event_live_id
19 | for room_id in room_ids:
20 | event_project = event_project_index[room_id] if room_id != 0 else None
21 | EventConferenceRoom.create_for_entity(event, event_project)
22 |
23 | event.is_activated = True
24 | event.save()
25 | event.recache()
26 | for ep in event_projects:
27 | ep.recache()
28 |
--------------------------------------------------------------------------------
/common/management/commands/deactivate_event.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 |
3 |
4 | class Command(BaseCommand):
5 | def add_arguments(self, parser):
6 | parser.add_argument(
7 | 'event_id'
8 | )
9 |
10 | def handle(self, *args, **options):
11 | from civictechprojects.models import Event
12 | event = Event.get_by_id_or_slug(options['event_id'])
13 | event_projects = event.get_event_projects()
14 | event.is_activated = False
15 | event.save()
16 | event.recache()
17 | for ep in event_projects:
18 | ep.recache()
19 |
--------------------------------------------------------------------------------
/common/management/commands/merge_with_salesforce.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | from civictechprojects.models import Project, VolunteerRelation, TaggedVolunteerRole
3 | from democracylab.models import Contributor
4 | from salesforce import contact as salesforce_contact, campaign as salesforce_campaign
5 | import traceback
6 |
7 |
8 | class Command(BaseCommand):
9 | def handle(self, *args, **options):
10 | contributors = Contributor.objects.filter(is_active__exact=True)
11 | for contributor in contributors:
12 | try:
13 | salesforce_contact.save(contributor)
14 | except Exception:
15 | print(f'Error merging user in Salesforce: ({contributor.id}) {contributor.username}')
16 | print(traceback.format_exc())
17 |
18 | projects = Project.objects.filter(is_searchable__exact=True)
19 | for project in projects:
20 | try:
21 | salesforce_campaign.save(project)
22 | except Exception:
23 | print(f'Error merging project in Salesforce: ({project.id}) {project.project_name}')
24 | print(traceback.format_exc())
25 |
26 |
--------------------------------------------------------------------------------
/common/management/commands/tags_csv.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | from common.models.tags import Tag
3 |
4 | class Command(BaseCommand):
5 | def handle(self, *args, **options):
6 | print("Canonical Name(lowercase + '-'),Display Name(shown on UI),Caption (Hover-over text in UI),Category,Subcategory,Parent (Use Canonical Name)")
7 | for tag in Tag.objects.all():
8 | fields = (tag.tag_name,tag.display_name,tag.caption,tag.category,tag.subcategory,tag.parent)
9 | print(','.join(fields))
10 |
--------------------------------------------------------------------------------
/common/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.7 on 2017-10-01 18:48
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Tag',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('tag_name', models.CharField(max_length=100, unique=True)),
21 | ('display_name', models.CharField(max_length=200)),
22 | ('caption', models.CharField(blank=True, max_length=500)),
23 | ('category', models.CharField(max_length=200)),
24 | ('subcategory', models.CharField(blank=True, max_length=200)),
25 | ('parent', models.CharField(blank=True, max_length=100)),
26 | ]
27 | )
28 | ]
29 |
--------------------------------------------------------------------------------
/common/migrations/0002_auto_20190501_1846.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.20 on 2019-05-01 18:46
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('common', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterModelOptions(
16 | name='tag',
17 | options={'ordering': ['category', 'subcategory', 'tag_name']},
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/common/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/common/migrations/__init__.py
--------------------------------------------------------------------------------
/common/models/__init__.py:
--------------------------------------------------------------------------------
1 | from .tags import Tag
--------------------------------------------------------------------------------
/common/models/visibility.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | # This should always be kept in sync with /common/components/common/TagCategory.jsx
5 | class Visibility(Enum):
6 | PUBLIC = 'PUBLIC'
7 |
--------------------------------------------------------------------------------
/common/static/js/selection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Initialize multi-selection box
3 | * @requires selectize.js (https://github.com/selectize/selectize.js/)
4 | *
5 | * @param selectElementId Id of select element
6 | * @param config Selectize configuration options
7 | */
8 |
9 | function initializeSelectionControl(selectElementId, config) {
10 | //Merge configuration settings in with defaults
11 | config = _.merge(config || {}, {
12 | persist: false,
13 | maxItems: null,
14 | hideSelected: true,
15 | closeAfterSelect: true,
16 | allowEmptyOption: true,
17 | });
18 |
19 | $("#" + selectElementId).selectize(config);
20 | }
21 |
--------------------------------------------------------------------------------
/common/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/common/tests/__init__.py
--------------------------------------------------------------------------------
/common/tests/test_redirectors.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from common.helpers.redirectors import InvalidArgumentsRedirector, DirtyUrlsRedirector, DeprecatedUrlsRedirector
3 |
4 |
5 | class RedirectorTests(TestCase):
6 |
7 | def test_InvalidArgumentsRedirector(self):
8 | invariants = ['/my/projects?projectAwaitingApproval=Project%202', '/']
9 | test_pairs = [[x, None] for x in invariants] + [['/projects/497?section=AboutProject&id=486', '/projects/497']]
10 |
11 | for test_pair in test_pairs:
12 | self.assertEqual(InvalidArgumentsRedirector.redirect_to(test_pair[0]), test_pair[1])
13 |
14 | def test_DirtyUrlsRedirector(self):
15 | before = '/projects?sortField=-project_date_modified&issues=education'
16 | after = '/projects?sortField=-project_date_modified&issues=education'
17 | self.assertEqual(DirtyUrlsRedirector.redirect_to(before), after)
18 |
19 | def test_DeprecatedUrlsRedirector(self):
20 | test_pairs = ['/about/partner', '/events/corporate']
21 | after = 'http://127.0.0.1:8000/companies'
22 | for test_pair in test_pairs:
23 | self.assertEqual(DeprecatedUrlsRedirector.redirect_to(test_pair), after)
24 |
--------------------------------------------------------------------------------
/democracylab/__init__.py:
--------------------------------------------------------------------------------
1 | INSTALLED_APPS = [
2 | 'civictechprojects.apps.CivictechprojectsConfig',
3 | 'oauth2.apps.OAuth2Config',
4 | 'django.contrib.admin',
5 | 'django.contrib.auth',
6 | 'django.contrib.contenttypes',
7 | 'django.contrib.sessions',
8 | 'django.contrib.messages',
9 | 'django.contrib.staticfiles',
10 | ]
--------------------------------------------------------------------------------
/democracylab/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Contributor
4 |
5 | contributor_text_fields = ['email', 'first_name', 'last_name', 'about_me', 'country', 'postal_code', 'phone_primary', 'about_me', 'uuid', 'qiqo_uuid']
6 | contributor_filter_fields = ('email_verified',)
7 | class ContributorAdmin(admin.ModelAdmin):
8 | list_display = tuple(contributor_text_fields) + contributor_filter_fields
9 | search_fields = contributor_text_fields
10 | list_filter = contributor_filter_fields
11 | def save_model(self, request, obj, form, change):
12 | super().save_model(request, obj, form, change)
13 | if change:
14 | obj.update_linked_items()
15 |
16 |
17 | admin.site.register(Contributor, ContributorAdmin)
--------------------------------------------------------------------------------
/democracylab/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class DemocracylabConfig(AppConfig):
5 | name = 'democracylab'
6 |
--------------------------------------------------------------------------------
/democracylab/migrations/0002_contributor_email_verified.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.7 on 2018-03-30 00:05
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('democracylab', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='contributor',
17 | name='email_verified',
18 | field=models.BooleanField(default=False),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/democracylab/migrations/0004_auto_20190429_1929.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.20 on 2019-04-29 19:29
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('democracylab', '0003_auto_20180911_0124'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='contributor',
17 | name='about_me',
18 | field=models.CharField(blank=True, max_length=100000, null=True),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/democracylab/migrations/0005_auto_20190503_2237.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.7 on 2019-05-03 22:37
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('democracylab', '0004_auto_20190429_1929'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='contributor',
17 | name='about_me',
18 | field=models.CharField(blank=True, default='', max_length=100000),
19 | preserve_default=False,
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/democracylab/migrations/0006_auto_20200427_2336.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.28 on 2020-04-27 23:36
3 | from __future__ import unicode_literals
4 |
5 | import democracylab.models
6 | from django.db import migrations, models
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | ('democracylab', '0005_auto_20190503_2237'),
13 | ]
14 |
15 | operations = [
16 | migrations.AddField(
17 | model_name='contributor',
18 | name='qiqo_uuid',
19 | field=models.CharField(blank=True, max_length=50),
20 | ),
21 | migrations.AddField(
22 | model_name='contributor',
23 | name='uuid',
24 | field=models.CharField(blank=True, default=democracylab.models.generate_uuid, max_length=32),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/democracylab/migrations/0007_backfill_user_uuids.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.28 on 2020-04-27 23:36
3 | from __future__ import unicode_literals
4 | from django.db import migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('democracylab', '0006_auto_20200427_2336'),
11 | ]
12 |
13 | # Don't do this automatically, as this breaks subsequent model changes if not done as the final migration step
14 | # operations = [migrations.RunPython(backfill_user_uuids)]
15 |
--------------------------------------------------------------------------------
/democracylab/migrations/0008_contributor_qiqo_signup_time.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.28 on 2020-11-06 18:07
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('democracylab', '0007_backfill_user_uuids'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='contributor',
17 | name='qiqo_signup_time',
18 | field=models.DateTimeField(blank=True, null=True),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/democracylab/migrations/0009_auto_20210302_2036.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.4 on 2021-03-02 20:36
2 |
3 | from django.db import migrations
4 | import taggit.managers
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('taggit', '0003_taggeditem_add_unique_index'),
11 | ('democracylab', '0008_contributor_qiqo_signup_time'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='contributor',
17 | name='user_technologies',
18 | field=taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', related_name='technology_users', through='democracylab.UserTaggedTechnologies', to='taggit.Tag', verbose_name='Tags'),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/democracylab/migrations/0010_alter_usertaggedtechnologies_tag.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2 on 2023-06-20 03:42
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("taggit", "0003_taggeditem_add_unique_index"),
11 | ("democracylab", "0009_auto_20210302_2036"),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name="usertaggedtechnologies",
17 | name="tag",
18 | field=models.ForeignKey(
19 | on_delete=django.db.models.deletion.CASCADE,
20 | related_name="%(app_label)s_%(class)s_items",
21 | to="taggit.tag",
22 | ),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/democracylab/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/democracylab/migrations/__init__.py
--------------------------------------------------------------------------------
/democracylab/templates/account/messages/logged_in.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/democracylab/templates/account/messages/logged_in.txt
--------------------------------------------------------------------------------
/democracylab/templates/emails/html_email_header.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
14 |
--------------------------------------------------------------------------------
/democracylab/templates/emails/html_email_headerleft.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/democracylab/templates/emails/html_email_paragraph.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
12 | {{text |safe}}
13 | |
14 |
--------------------------------------------------------------------------------
/democracylab/templates/emails/html_email_paragraph_center.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
12 | {{text |safe}}
13 | |
14 |
--------------------------------------------------------------------------------
/democracylab/templates/emails/html_email_subheader.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/democracylab/templates/emails/html_email_text_line.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
11 | {{text |safe}}
12 | |
13 |
14 |
--------------------------------------------------------------------------------
/democracylab/templates/registration/password_reset_complete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 | Your password has been set. You may go ahead and
6 | login
7 | now.
8 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/democracylab/templates/registration/password_reset_confirm.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 | {% if validlink %}
5 | Change password
6 |
11 | {% else %}
12 |
13 | The password reset link was invalid, possibly because it has already been
14 | used. Please request a new password reset.
15 |
16 | {% endif %}
17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/democracylab/templates/registration/password_reset_done.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 | We've emailed you instructions for setting your password, if an account
6 | exists with the email you entered.
7 | You should receive them shortly.
8 |
9 |
10 | If you don't receive an email, please make sure you've entered the address
11 | you registered with,
12 | and check your spam folder.
13 |
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/democracylab/templates/registration/password_reset_email.html:
--------------------------------------------------------------------------------
1 | {% autoescape off %}
2 | To initiate the password reset process for your {{ user.get_username }} DemocracyLab account,
3 | click the link below:
4 |
5 | {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
6 |
7 | If clicking the link above doesn't work, please copy and paste the URL in a new browser
8 | window instead.
9 |
10 | Sincerely,
11 | The DemocracyLab Team
12 | {% endautoescape %}
13 |
--------------------------------------------------------------------------------
/democracylab/templates/registration/password_reset_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 | Forgot password
5 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/democracylab/templates/registration/password_reset_subject.txt:
--------------------------------------------------------------------------------
1 | Reset your DemocracyLab Password
2 |
--------------------------------------------------------------------------------
/democracylab/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for democracylab project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "democracylab.settings")
15 |
16 | application = get_wsgi_application()
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | ### Postgres credentials ###
2 | POSTGRES_USER=postgres
3 | POSTGRES_PASSWORD=change_me_asap
4 | POSTGRES_DB=postgres
5 | POSTGRES_HOST=db
6 |
7 | ### AWS Configs ###
8 | S3_BUCKET=s3_bucket_name
9 |
10 | ### Google reCAPTCHA keys ###
11 | GOOGLE_RECAPTCHA_SECRET_KEY=change_me_asap
12 | GOOGLE_RECAPTCHA_SITE_KEY=change_me_asap
13 |
14 | ### Email Configs ###
15 | ADMIN_EMAIL=change_me_asap
16 |
17 | ### PayPal Configs ###
18 | PAYPAL_ENDPOINT=url_for_paypal_integration
19 | PAYPAL_PAYEE=paypal_email_address_here
20 |
21 | ### Misc Project Configs ###
22 | DJANGO_SECRET_KEY=change_me_asap
23 | STATIC_CDN_URL=https://d1agxr2dqkgkuy.cloudfront.net
24 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 |
6 | if __name__ == "__main__":
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "democracylab.settings")
8 | try:
9 | from django.core.management import execute_from_command_line
10 | except ImportError:
11 | # The above import may fail for some other reason. Ensure that the
12 | # issue is really that Django is missing to avoid masking other
13 | # exceptions on Python 2.
14 | try:
15 | import django
16 | except ImportError:
17 | raise ImportError(
18 | "Couldn't import Django. Are you sure it's installed and "
19 | "available on your PYTHONPATH environment variable? Did you "
20 | "forget to activate a virtual environment?"
21 | )
22 | raise
23 | execute_from_command_line(sys.argv)
24 |
25 |
26 |
--------------------------------------------------------------------------------
/oauth2/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class OAuth2Config(AppConfig):
5 | name = 'oauth2'
6 |
--------------------------------------------------------------------------------
/oauth2/providers/facebook/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/oauth2/providers/facebook/__init__.py
--------------------------------------------------------------------------------
/oauth2/providers/facebook/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 |
4 | class FacebookConnectForm(forms.Form):
5 | access_token = forms.CharField(required=True)
6 |
--------------------------------------------------------------------------------
/oauth2/providers/facebook/templates/facebook/fbconnect.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/oauth2/providers/facebook/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import re_path
2 |
3 | from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
4 |
5 | from . import views
6 | from .provider import FacebookProvider
7 |
8 |
9 | urlpatterns = default_urlpatterns(FacebookProvider)
10 |
11 | urlpatterns += [
12 | re_path(r'^facebook/login/token/$',
13 | views.login_by_token,
14 | name="facebook_login_by_token"),
15 | ]
16 |
--------------------------------------------------------------------------------
/oauth2/providers/github/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/oauth2/providers/github/__init__.py
--------------------------------------------------------------------------------
/oauth2/providers/github/urls.py:
--------------------------------------------------------------------------------
1 | from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
2 |
3 | from .provider import GitHubProvider
4 |
5 |
6 | urlpatterns = default_urlpatterns(GitHubProvider)
7 |
--------------------------------------------------------------------------------
/oauth2/providers/google/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/oauth2/providers/google/__init__.py
--------------------------------------------------------------------------------
/oauth2/providers/google/urls.py:
--------------------------------------------------------------------------------
1 | from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
2 |
3 | from .provider import GoogleProvider
4 |
5 |
6 | urlpatterns = default_urlpatterns(GoogleProvider)
7 |
--------------------------------------------------------------------------------
/oauth2/providers/google/views.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | from allauth.socialaccount.providers.oauth2.views import (
4 | OAuth2Adapter,
5 | OAuth2CallbackView,
6 | OAuth2LoginView,
7 | )
8 |
9 | from .provider import GoogleProvider
10 |
11 |
12 | class GoogleOAuth2Adapter(OAuth2Adapter):
13 | provider_id = GoogleProvider.id
14 | access_token_url = 'https://accounts.google.com/o/oauth2/token'
15 | authorize_url = 'https://accounts.google.com/o/oauth2/auth'
16 | profile_url = 'https://www.googleapis.com/oauth2/v1/userinfo'
17 |
18 | def complete_login(self, request, app, token, **kwargs):
19 | resp = requests.get(self.profile_url,
20 | params={'access_token': token.token,
21 | 'alt': 'json'})
22 | resp.raise_for_status()
23 | extra_data = resp.json()
24 | login = self.get_provider() \
25 | .sociallogin_from_response(request,
26 | extra_data)
27 | return login
28 |
29 |
30 | oauth2_login = OAuth2LoginView.adapter_view(GoogleOAuth2Adapter)
31 | oauth2_callback = OAuth2CallbackView.adapter_view(GoogleOAuth2Adapter)
32 |
--------------------------------------------------------------------------------
/oauth2/providers/linkedin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/oauth2/providers/linkedin/__init__.py
--------------------------------------------------------------------------------
/oauth2/providers/linkedin/urls.py:
--------------------------------------------------------------------------------
1 | from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
2 |
3 | from .provider import LinkedInOAuth2Provider
4 |
5 |
6 | urlpatterns = default_urlpatterns(LinkedInOAuth2Provider)
7 |
--------------------------------------------------------------------------------
/oauth2/socialapp.py:
--------------------------------------------------------------------------------
1 | """
2 | Decouples SocialApp client credentials from the database
3 | """
4 | from django.conf import settings
5 |
6 |
7 | class SocialAppMixin:
8 | class Meta:
9 | abstract = True
10 |
11 | # Get credentials to be used by OAuth2Client
12 | def get_app(self, request):
13 | app = settings.SOCIAL_APPS.get(self.id)
14 | from allauth.socialaccount.models import SocialApp
15 | return SocialApp(
16 | id=app.get('id'),
17 | name='SocialApp instance',
18 | provider=self.id,
19 | client_id=app.get('client_id'),
20 | secret=app.get('secret'),
21 | key=''
22 | )
23 |
--------------------------------------------------------------------------------
/release-tasks.sh:
--------------------------------------------------------------------------------
1 | python manage.py migrate
2 | python manage.py collectstatic --noinput --clear
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3==1.26.114
2 | botocore==1.29.114
3 | dj-database-url==0.4.1
4 | Django==4.2.3
5 | django-allauth==0.50
6 | django-appconf==1.0.2
7 | django-compressor==2.1.1
8 | django-core==1.4.1
9 | django-cors-headers==3.10.1
10 | django-csp==3.7
11 | django-registration==3.1.2
12 | django-rq==2.4.1
13 | django-s3direct==0.4.2
14 | django-storages==1.5.1
15 | django-taggit==4.0
16 | django-timezone-field==4.2.3
17 | django-debreach==2.0.1
18 | djangorestframework==3.14.0
19 | docutils==0.14
20 | gunicorn==19.6.0
21 | jmespath==0.9.3
22 | mailchimp3==3.0.21
23 | psycopg2==2.9.6
24 | psycopg2-binary==2.9.6
25 | python-dateutil==2.8.2
26 | pytz==2023.3
27 | rcssmin==1.0.6
28 | redis==4.3.6
29 | rq==1.10.0
30 | requests==2.31.0
31 | rjsmin==1.0.12
32 | s3transfer==0.6.0
33 | simplejson==3.12.0
34 | six==1.15.0
35 | whitenoise==5.2.0
36 | newrelic
37 | psutil==5.9.0
38 |
--------------------------------------------------------------------------------
/salesforce/README:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/salesforce/README
--------------------------------------------------------------------------------
/salesforce/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/salesforce/__init__.py
--------------------------------------------------------------------------------
/salesforce/apps.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DemocracyLab/CivicTechExchange/fb9ec4d627add9f3974444fb1de009b5bdf7e521/salesforce/apps.py
--------------------------------------------------------------------------------
/salesforce/volunteer_hours.py:
--------------------------------------------------------------------------------
1 | from .client import SalesforceClient
2 | import json
3 | import requests
4 | import threading
5 |
6 | ''' VolunteerRelation model maps to the Volunteer Hours object in Salesforce '''
7 | client = SalesforceClient.get_instance()
8 |
--------------------------------------------------------------------------------
/setupJest.js:
--------------------------------------------------------------------------------
1 | global.fetch = require("jest-fetch-mock");
2 |
--------------------------------------------------------------------------------
/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const common = require('./webpack.common.js');
3 |
4 | module.exports = merge(common, {
5 | mode: 'development',
6 | devtool: 'source-map',
7 | });
8 |
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const common = require('./webpack.common.js');
3 | const TerserPlugin = require('terser-webpack-plugin');
4 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
5 |
6 | module.exports = merge(common, {
7 | mode: 'production',
8 | devtool: '',
9 | optimization: {
10 | minimize: true,
11 | minimizer: [
12 | new OptimizeCSSAssetsPlugin({}),
13 | new TerserPlugin({
14 | terserOptions: {
15 | output: {
16 | comments: false,
17 | },
18 | },
19 | extractComments: false,
20 | })
21 | ],
22 | },
23 | });
24 |
--------------------------------------------------------------------------------