Skip to content

Django v4.2 and Package Upgrades #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,30 @@
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
"name": "CopWatch",

// Update the 'dockerComposeFile' list if you have more compose files or use different names.
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
"dockerComposeFile": [
"../docker-compose.yml",
"docker-compose.yml"
],

// The 'service' property is the name of the service for the container that VS Code should
// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
"service": "django",

// The optional 'workspaceFolder' property is the path VS Code should open by default when
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
"workspaceFolder": "/code",

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Uncomment the next line if you want start specific services in your Docker Compose config.
// "runServices": [],

// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
"shutdownAction": "stopCompose",

// Uncomment the next line to run commands after the container is created - for example installing curl.
"postCreateCommand": "sudo chown appuser /home/appuser/.cache /code/frontend/node_modules /code/venv /code/public /var/run/docker.sock",

// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "appuser",

// A command to run each time the container is successfully started.
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",

"customizations": {
"vscode": {
"extensions": [
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
echo "ENV_URL=https://nccopwatch.org/" >> $GITHUB_ENV
- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.12'
cache: 'pip'
cache-dependency-path: 'requirements/*/*.txt'
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.12'
cache: 'pip'
cache-dependency-path: 'requirements/*/*.txt'
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ repos:
rev: 24.10.0
hooks:
- id: black
language_version: python3.10
language_version: python3.12
exclude: migrations
- repo: https://github.com/PyCQA/flake8
rev: 7.1.1
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ RUN npm install --silent
COPY frontend/ /code/
RUN npm run build

FROM python:3.10-slim-bullseye AS base
FROM python:3.12-slim-bullseye AS base

# Create a group and user to run our app
ARG APP_USER=appuser
Expand Down Expand Up @@ -95,7 +95,7 @@ ENTRYPOINT ["/code/docker-entrypoint.sh"]
# Start uWSGI
CMD ["newrelic-admin", "run-program", "uwsgi", "--single-interpreter", "--enable-threads", "--show-config"]

FROM python:3.10-slim-bullseye AS dev
FROM python:3.12-slim-bullseye AS dev

ARG USERNAME=appuser
ARG USER_UID=1000
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ update_requirements:
pip-compile --output-file=requirements/base/base.txt requirements/base/base.in
pip-compile --output-file=requirements/test/test.txt requirements/test/test.in
pip-compile --output-file=requirements/dev/dev.txt requirements/dev/dev.in
pip-compile --upgrade --output-file=requirements/deploy/deploy.txt requirements/deploy/deploy.in
pip-compile --output-file=requirements/deploy/deploy.txt requirements/deploy/deploy.in

install_requirements:
@echo 'Installing pip-tools...'
Expand Down
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# NC Traffic Stops documentation build configuration file, created by
# sphinx-quickstart on Sat Sep 6 15:13:52 2014.
Expand Down
6 changes: 3 additions & 3 deletions docs/dev-setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Below you will find basic setup and deployment instructions for the NC Traffic
Stops project. To begin you should have the following applications installed on
your local development system:

- Python 3.10
- Python 3.12
- NodeJS >= 12.6.0
- `pip >= 8 or so <http://www.pip-installer.org/>`_
- Postgres >= 16
Expand Down Expand Up @@ -85,8 +85,8 @@ To use ``psql`` locally, make sure you have the following env variables loaded
To setup your local environment you should create a virtualenv and install the
necessary requirements::

$ which python3.11 # make sure you have Python 3.11 installed
$ mkvirtualenv --python=`which python3.11` traffic-stops
$ which python3.12 # make sure you have Python 3.10 installed
$ mkvirtualenv --python=`which python3.10` traffic-stops
(traffic-stops)$ pip install -U pip
(traffic-stops)$ make setup

Expand Down
14 changes: 6 additions & 8 deletions nc/admin.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from ckeditor.widgets import CKEditorWidget
from django import forms
from django.contrib import admin
from django_ckeditor_5.widgets import CKEditor5Widget

from nc.models import Agency, Resource, ResourceFile, StopSummary


@admin.register(Agency)
class AgencyAdmin(admin.ModelAdmin):
list_display = ("name", "id", "census_profile_id")
search_fields = ("name",)
ordering = ("id",)


@admin.register(StopSummary)
class StopSummaryAdmin(admin.ModelAdmin):
list_display = (
"id",
Expand Down Expand Up @@ -56,14 +58,15 @@ class InlineResourceFile(admin.StackedInline):

class ResourceForm(forms.ModelForm):
# https://django-ckeditor.readthedocs.io/en/latest/#widget
title = forms.CharField(widget=CKEditorWidget())
description = forms.CharField(widget=CKEditorWidget())
title = forms.CharField(widget=CKEditor5Widget())
description = forms.CharField(widget=CKEditor5Widget())

class Meta:
model = Resource
fields = "__all__"


@admin.register(Resource)
class ResourceAdmin(admin.ModelAdmin):
fields = (
"agencies",
Expand Down Expand Up @@ -93,8 +96,3 @@ def save_model(self, request, obj, form, change):
def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change)
form.instance.agencies.set(form.cleaned_data["agencies"], clear=True)


admin.site.register(Agency, AgencyAdmin)
admin.site.register(StopSummary, StopSummaryAdmin)
admin.site.register(Resource, ResourceAdmin)
14 changes: 7 additions & 7 deletions nc/data/download_from_nc.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ def nc_download_and_unzip_data(destination, prefix="state-"):
# make sure destination exists or create a temporary directory
if not destination:
destination = tempfile.mkdtemp(prefix=prefix)
logger.debug("Created temp directory {}".format(destination))
logger.debug(f"Created temp directory {destination}")
else:
if not os.path.exists(destination):
os.makedirs(destination)
logger.info("Created {}".format(destination))
logger.info(f"Created {destination}")
zip_basename = date.today().strftime("NC_STOPS_Extract_%Y_%m_%d.zip")
zip_filename = os.path.join(destination, zip_basename)
# don't re-download data if raw data file already exists
if os.path.exists(zip_filename):
logger.debug("{} exists, skipping download".format(zip_filename))
logger.debug(f"{zip_filename} exists, skipping download")
else:
logger.debug("Downloading data to {}".format(zip_filename))
logger.debug(f"Downloading data to {zip_filename}")
nc_data_site = settings.NC_FTP_HOST
nc_data_file = "STOPS_Extract.zip"
nc_data_directory = "/TSTOPextract"
Expand All @@ -52,11 +52,11 @@ def nc_download_and_unzip_data(destination, prefix="state-"):
listing = ftps.retrlines("LIST", show_ftp_listing)
line = listing.split("\n")[0]
if not line.startswith("226 "): # server's "Transfer complete" message
raise ValueError("Expected 226 response from ftp server, got %r" % listing)
raise ValueError(f"Expected 226 response from ftp server, got {listing!r}")
logger.info('Downloading "%s"...', nc_data_file)
with open(zip_filename, "wb") as f:
ftps.retrbinary("RETR %s" % nc_data_file, f.write)
logger.info('File written to "%s"' % zip_filename)
ftps.retrbinary(f"RETR {nc_data_file}", f.write)
logger.info(f'File written to "{zip_filename}"')

unzip_data(destination, zip_path=zip_filename)
return destination
16 changes: 8 additions & 8 deletions nc/data/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ def to_standard_csv(input_path, output_path):
quoting=csv.QUOTE_MINIMAL,
skipinitialspace=False,
)
with open(input_path, "rt") as input:
with open(output_path, "wt") as output:
with open(input_path) as input:
with open(output_path, "w") as output:
reader = csv.reader(input, dialect="nc_data_in")
writer = csv.writer(output, dialect="nc_data_out")
headings_written = False
Expand All @@ -186,20 +186,20 @@ def convert_to_csv(destination):
continue
csv_path = data_path.replace(".txt", ".csv")
if os.path.exists(csv_path):
logger.info("{} already exists, skipping csv conversion".format(csv_path))
logger.info(f"{csv_path} already exists, skipping csv conversion")
continue
logger.info("Converting {} > {}".format(data_path, csv_path))
logger.info(f"Converting {data_path} > {csv_path}")
# Edit source data .txt file in-place to remove NUL bytes
# (only seen in Stop.txt)
call([r"sed -i 's/\x0//g' {}".format(data_path)], shell=True)
call([rf"sed -i 's/\x0//g' {data_path}"], shell=True)
to_standard_csv(data_path, csv_path)
data_count = line_count(data_path)
csv_count = line_count(csv_path)
if data_count == (csv_count - 1):
logger.debug("CSV line count matches original data file: {}".format(data_count))
logger.debug(f"CSV line count matches original data file: {data_count}")
else:
logger.error("DAT {}".format(data_count))
logger.error("CSV {}".format(csv_count))
logger.error(f"DAT {data_count}")
logger.error(f"CSV {csv_count}")


def update_nc_agencies(nc_csv_path, destination):
Expand Down
3 changes: 0 additions & 3 deletions nc/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models


Expand Down
3 changes: 0 additions & 3 deletions nc/migrations/0002_agency_census_profile_id.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models


Expand Down
3 changes: 0 additions & 3 deletions nc/migrations/0003_auto_20180115_1141.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models


Expand Down
2 changes: 1 addition & 1 deletion nc/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class Agency(models.Model):
census_profile_id = models.CharField(max_length=16, blank=True, default="")
last_reported_stop = models.DateField(null=True)

class Meta(object):
class Meta:
verbose_name_plural = "Agencies"

def __str__(self):
Expand Down
2 changes: 1 addition & 1 deletion nc/prime_cache.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logging
import time

from collections.abc import Generator
from contextlib import contextmanager
from typing import Generator

import boto3
import httpx
Expand Down
2 changes: 1 addition & 1 deletion nc/tests/api/test_arrests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def reverse_querystring(
"""
base_url = reverse(view, urlconf=urlconf, args=args, kwargs=kwargs, current_app=current_app)
if query_kwargs:
return "{}?{}".format(base_url, urlencode(query_kwargs))
return f"{base_url}?{urlencode(query_kwargs)}"
return base_url


Expand Down
2 changes: 1 addition & 1 deletion nc/tests/api/test_basic_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
pytestmark = pytest.mark.django_db


RACE_VALUES = set([v[0] for v in RACE_CHOICES])
RACE_VALUES = {v[0] for v in RACE_CHOICES}


def test_no_agency(client, search_url):
Expand Down
6 changes: 3 additions & 3 deletions nc/tests/api/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_stop_date_after_august_excludes_july_stop(client, search_url, durham, j
data={"agency": durham.pk, "stop_date_after": dt.date(2020, 8, 1)},
format="json",
)
stop_ids = set([stop["stop_id"] for stop in response.data["results"]])
stop_ids = {stop["stop_id"] for stop in response.data["results"]}
assert july_person.stop.stop_id not in stop_ids


Expand All @@ -41,7 +41,7 @@ def test_stop_date_after_august_includes_august_stop(client, search_url, durham,
data={"agency": durham.pk, "stop_date_after": dt.date(2020, 8, 1)},
format="json",
)
stop_ids = set([stop["stop_id"] for stop in response.data["results"]])
stop_ids = {stop["stop_id"] for stop in response.data["results"]}
assert {august_person.stop.stop_id} == stop_ids
assert august_person.stop.date == response.data["results"][0]["date"]

Expand All @@ -53,6 +53,6 @@ def test_stop_date_after_july_includes_both(client, search_url, durham, july_per
data={"agency": durham.pk, "stop_date_after": dt.date(2020, 7, 1)},
format="json",
)
stop_ids = set([stop["stop_id"] for stop in response.data["results"]])
stop_ids = {stop["stop_id"] for stop in response.data["results"]}
assert july_person.stop.stop_id in stop_ids
assert august_person.stop.stop_id in stop_ids
10 changes: 5 additions & 5 deletions nc/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ def refresh_view(obj, create, extracted, **kwargs):


class AgencyFactory(ViewRefreshFactory):
class Meta(object):
class Meta:
model = models.Agency

name = factory.Sequence(lambda n: "Agency %03d" % n)


class PersonFactory(ViewRefreshFactory):
class Meta(object):
class Meta:
model = models.Person

person_id = factory.Sequence(lambda x: x)
Expand All @@ -38,7 +38,7 @@ class Meta(object):


class StopFactory(ViewRefreshFactory):
class Meta(object):
class Meta:
model = models.Stop

stop_id = factory.Sequence(lambda x: x)
Expand All @@ -61,7 +61,7 @@ def year(self, create, extracted, **kwargs):


class SearchFactory(ViewRefreshFactory):
class Meta(object):
class Meta:
model = models.Search

search_id = factory.Sequence(lambda x: x)
Expand All @@ -71,7 +71,7 @@ class Meta(object):


class ContrabandFactory(ViewRefreshFactory):
class Meta(object):
class Meta:
model = models.Contraband

contraband_id = factory.Sequence(lambda x: x)
Expand Down
2 changes: 1 addition & 1 deletion nc/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def test_officer_stops_count(self):
ethnicity="H", stop__agency=agency, stop__year=2017, stop__officer_id=p1.stop.officer_id
)
url = reverse("nc:agency-api-stops", args=[agency.pk])
url = "{}?officer={}".format(url, p1.stop.officer_id)
url = f"{url}?officer={p1.stop.officer_id}"
response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
Expand Down
5 changes: 2 additions & 3 deletions nc/urls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path, re_path
from django.urls import include, path
from django.views.decorators.csrf import csrf_exempt
from rest_framework.routers import DefaultRouter

Expand All @@ -13,7 +12,7 @@


urlpatterns = [ # noqa
re_path(r"^api/", include(router.urls)),
path("api/", include(router.urls)),
path("api/about/contact/", csrf_exempt(views.ContactView.as_view()), name="contact-form"),
path(
"api/agency/<agency_id>/year-range/",
Expand Down
Loading