diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 983e022..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = { - globals: { - __PATH_PREFIX__: true, - }, - extends: ["eslint:recommended", "plugin:react/recommended", "prettier"], - rules: { - indent: ["error", 2], - "no-console": "off", - strict: ["error", "global"], - curly: "warn", - semi: ["error", "never"], - "space-in-parens": ["error", "never"], - "space-before-blocks": ["error", "always"], - "react/prop-types": "off", - }, - ignorePatterns: [ - "**/lib/*", - "venv/**/*", - "/static/**/*", - "/staticfiles/**/*", - ], - parserOptions: { - sourceType: "module", - allowImportExportEverywhere: true, - }, - env: { - browser: true, - node: true, - es2022: true, - }, -} diff --git a/.flake8 b/.flake8 deleted file mode 100644 index a7ac0e9..0000000 --- a/.flake8 +++ /dev/null @@ -1,9 +0,0 @@ -[flake8] -exclude = - venv, - **/migrations/*, - node_modules, -# So flake8 plays nicely with black -# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html -max-line-length = 88 -extend-ignore = E203 diff --git a/.github/docker-compose.yml b/.github/docker-compose.yml deleted file mode 100644 index baac29f..0000000 --- a/.github/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - webpack: - build: - dockerfile: Dockerfile.deploy - target: "" - - app: - build: - dockerfile: Dockerfile.deploy - target: "" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e8fa5b8..38bd0e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Lint code and run tests +name: Run tests on: push: @@ -10,45 +10,8 @@ on: - main jobs: - lint: - name: Run linters - runs-on: ubuntu-latest - - steps: - - name: Check out Git repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.14.3" - - - name: Install Python dependencies - run: pip install black flake8 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: "25" - - # ESLint and Prettier must be in `package.json` - - name: Install Node.js dependencies - run: npm ci - - - name: Run linters - uses: wearerequired/lint-action@v2 - with: - continue_on_error: false - black: true - black_args: "--check . --exclude 'map/migrations|node_modules'" - flake8: true - flake8_args: "--config=.flake8" - eslint: true - prettier: true - test: name: Run tests - needs: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -57,6 +20,5 @@ jobs: - name: Run tests run: | docker compose -f docker-compose.yml \ - -f .github/docker-compose.yml \ -f tests/docker-compose.yml \ run --rm app diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml deleted file mode 100644 index 77ac3dd..0000000 --- a/.github/workflows/publish_docker_image.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Publish image to GitHub Container Registry - -on: - push: - branches: - - main - - deploy - workflow_dispatch: - -jobs: - push_to_registry: - name: Push Docker image to GitHub Packages - runs-on: ubuntu-latest - steps: - - name: Login to GitHub Container Registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create tag from branch - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - - - name: Push to GitHub Packages - uses: docker/build-push-action@v5 - with: - push: true - tags: ${{ steps.meta.outputs.tags }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 388670d..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,44 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace - - - repo: https://github.com/psf/black - rev: 26.1.0 - hooks: - - id: black - exclude: ^(map/migrations/) - - - repo: https://github.com/pycqa/flake8 - rev: "9f60881" - hooks: - - id: flake8 - exclude: map/migrations - args: [--max-line-length=88, --extend-ignore=E203] - - - repo: https://github.com/rtts/djhtml - rev: v1.5.1 # replace with the latest tag on GitHub - hooks: - - id: djhtml - # Indent only HTML files in template directories - files: .*/templates/.*\.html$ - args: [--tabwidth=2] # tabs should be 2 spaces in Django templates - - - repo: https://github.com/pre-commit/mirrors-eslint - rev: "1f7d592" - hooks: - - id: eslint - additional_dependencies: - - eslint@8.19.0 - - eslint-plugin-react@7.30.1 - - eslint-config-prettier@8.5.0 - - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.5.1 - hooks: - - id: prettier - exclude: ^(map/static/js/lib/|map/static/css/bootstrap|map/templates/) - files: \.(js|ts|jsx|tsx|css|scss|less|json|markdown|md|yaml|yml)$ diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index caeaeef..0000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -**/static/js/lib/ -**/static/css/bootstrap/ -**/templates/ diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index f6ca428..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "trailingComma": "es5", - "tabWidth": 2, - "semi": false, - "singleQuote": false -} diff --git a/Dockerfile.deploy b/Dockerfile.deploy deleted file mode 100644 index 7f8a7f5..0000000 --- a/Dockerfile.deploy +++ /dev/null @@ -1,16 +0,0 @@ -FROM ghcr.io/datamade/code-challenge-v2:main - -# Install any requirements not in the base image -COPY ./requirements.txt /app/requirements.txt -RUN pip install --no-cache-dir -r requirements.txt - -COPY ./package.json /app/package.json -RUN npm install - -COPY . /app - -# Bake static files into the image -RUN npm run build - -ENV DJANGO_SECRET_KEY 'foobar' -RUN python manage.py collectstatic --noinput diff --git a/heroku.yml b/heroku.yml deleted file mode 100644 index 60cedc3..0000000 --- a/heroku.yml +++ /dev/null @@ -1,12 +0,0 @@ -setup: - addons: - - plan: heroku-postgresql -build: - docker: - web: Dockerfile.deploy -release: - command: - - ./scripts/release.sh - image: web -run: - web: gunicorn -t 180 -w 3 --log-level debug map.wsgi:application diff --git a/map/migrations/0003_alter_communityarea_area_id_alter_communityarea_name_and_more.py b/map/migrations/0003_alter_communityarea_area_id_alter_communityarea_name_and_more.py new file mode 100644 index 0000000..666e761 --- /dev/null +++ b/map/migrations/0003_alter_communityarea_area_id_alter_communityarea_name_and_more.py @@ -0,0 +1,64 @@ +# Generated by Django 6.0.3 on 2026-03-12 16:12 + +import django.contrib.gis.db.models.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0002_communityarea'), + ] + + operations = [ + migrations.AlterField( + model_name='communityarea', + name='area_id', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='communityarea', + name='name', + field=models.CharField(blank=True, max_length=32, null=True), + ), + migrations.AlterField( + model_name='restaurantpermit', + name='community_area_id', + field=models.CharField(blank=True, max_length=2, null=True), + ), + migrations.AlterField( + model_name='restaurantpermit', + name='issue_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AlterField( + model_name='restaurantpermit', + name='location', + field=django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326), + ), + migrations.AlterField( + model_name='restaurantpermit', + name='permit_id', + field=models.CharField(blank=True, max_length=16, null=True), + ), + migrations.AlterField( + model_name='restaurantpermit', + name='permit_type', + field=models.CharField(blank=True, max_length=64, null=True), + ), + migrations.AlterField( + model_name='restaurantpermit', + name='street_direction', + field=models.CharField(blank=True, max_length=8, null=True), + ), + migrations.AlterField( + model_name='restaurantpermit', + name='street_name', + field=models.CharField(blank=True, max_length=32, null=True), + ), + migrations.AlterField( + model_name='restaurantpermit', + name='street_number', + field=models.CharField(blank=True, max_length=16, null=True), + ), + ] diff --git a/map/models.py b/map/models.py index e0436ef..2733741 100644 --- a/map/models.py +++ b/map/models.py @@ -3,21 +3,21 @@ class CommunityArea(models.Model): - name = models.CharField(max_length=32, null=False, blank=False) - area_id = models.IntegerField(null=False, blank=False) + name = models.CharField(max_length=32, null=True, blank=True) + area_id = models.IntegerField(null=True, blank=True) def __str__(self): return self.name.title() class RestaurantPermit(models.Model): - permit_id = models.CharField(max_length=16, null=False, blank=False) - permit_type = models.CharField(max_length=64, null=False, blank=False) + permit_id = models.CharField(max_length=16, null=True, blank=True) + permit_type = models.CharField(max_length=64, null=True, blank=True) application_start_date = models.DateField(null=True, blank=True) - issue_date = models.DateField(null=False, blank=False) + issue_date = models.DateField(null=True, blank=True) work_description = models.TextField(null=True, blank=True) - street_number = models.CharField(max_length=16, null=False, blank=False) - street_direction = models.CharField(max_length=8, null=False, blank=False) - street_name = models.CharField(max_length=32, null=False, blank=False) - location = gis_models.PointField(null=False, blank=False) - community_area_id = models.CharField(max_length=2, null=False, blank=False) + street_number = models.CharField(max_length=16, null=True, blank=True) + street_direction = models.CharField(max_length=8, null=True, blank=True) + street_name = models.CharField(max_length=32, null=True, blank=True) + location = gis_models.PointField(null=True, blank=True) + community_area_id = models.CharField(max_length=2, null=True, blank=True) diff --git a/map/serializers.py b/map/serializers.py index 4dba951..b1320ac 100644 --- a/map/serializers.py +++ b/map/serializers.py @@ -6,7 +6,7 @@ class CommunityAreaSerializer(serializers.ModelSerializer): class Meta: model = CommunityArea - fields = ["name", "area_id", "num_permits"] + fields = ["name", "num_permits"] num_permits = serializers.SerializerMethodField() diff --git a/requirements.txt b/requirements.txt index f3b771e..433ef72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ gunicorn==25.1.0 pip==25.3 psycopg2-binary==2.9.11 pytest==9.0.2 +pytest-django==4.12.0 sentry-sdk==2.54.0 setuptools==82.0.0 whitenoise==6.12.0 diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index f7aa294..ebb1387 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -6,4 +6,5 @@ services: DJANGO_SECRET_KEY: test-key # Disable manifest storage for testing DJANGO_STATICFILES_STORAGE: django.contrib.staticfiles.storage.StaticFilesStorage + DJANGO_SETTINGS_MODULE: map.settings command: pytest -sv diff --git a/tests/test_views.py b/tests/test_views.py index 7291d43..63c076c 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,9 +1,45 @@ -""" -See https://github.com/datamade/testing-guidelines for details on our approach to -testing and tutorials for writing tests for Django apps! -""" +import pytest +from datetime import date +from django.shortcuts import reverse +from rest_framework.test import APIClient -def test_example(): - print("Tests initialized! Feel free to remove this test and add new ones.") - return True +from map.models import CommunityArea, RestaurantPermit + + +@pytest.mark.django_db +def test_map_data_view(): + # Create some community areas + area1 = CommunityArea.objects.create(name="Beverly", area_id="1") + area2 = CommunityArea.objects.create(name="Lincoln Park", area_id="2") + + # Test permits for Beverly + RestaurantPermit.objects.create( + community_area_id=area1.area_id, issue_date=date(2021, 1, 15) + ) + RestaurantPermit.objects.create( + community_area_id=area1.area_id, issue_date=date(2021, 2, 20) + ) + + # Test permits for Lincoln Park + RestaurantPermit.objects.create( + community_area_id=area2.area_id, issue_date=date(2021, 3, 10) + ) + RestaurantPermit.objects.create( + community_area_id=area2.area_id, issue_date=date(2021, 2, 14) + ) + RestaurantPermit.objects.create( + community_area_id=area2.area_id, issue_date=date(2021, 6, 22) + ) + + client = APIClient() + response = client.get(reverse("map_data", query={"year": 2021})) + + expected_response = [ + {"name": "Beverly", "num_permits": 2}, + {"name": "Lincoln Park", "num_permits": 3}, + ] + + assert ( + response.json() == expected_response + ), "Should return the number of permits for each community area"