Skip to content

Commit

Permalink
Merge pull request #60 from OpenGeoscience/auth-and-projects
Browse files Browse the repository at this point in the history
Authentication and projects
  • Loading branch information
annehaley authored Oct 1, 2024
2 parents ad426e9 + b614f13 commit 6a3c0d4
Show file tree
Hide file tree
Showing 59 changed files with 1,259 additions and 407 deletions.
7 changes: 7 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ services:
# Log printing via Rich is enhanced by a TTY
tty: true
env_file: ./dev/.env.docker-compose
environment:
# ensure these match the web container
- DJANGO_HOMEPAGE_REDIRECT_URL=http://localhost:8080/
- VUE_APP_BASE_URL=http://localhost:8080/
- VUE_APP_OAUTH_CLIENT_ID=cBmD6D6F2YAmMWHNQZFPUr4OpaXVpW5w4Thod6Kj
volumes:
- .:/opt/uvdat-server
ports:
Expand All @@ -33,6 +38,8 @@ services:
]
# Docker Compose does not set the TTY width, which causes Celery errors
tty: false
environment:
- DJANGO_HOMEPAGE_REDIRECT_URL=http://localhost:8080/
env_file: ./dev/.env.docker-compose
volumes:
- .:/opt/uvdat-server
Expand Down
5 changes: 4 additions & 1 deletion docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
b. Run `docker-compose run --rm django ./manage.py createsuperuser`
and follow the prompts to create your own user.

c. Run `docker-compose run --rm django ./manage.py populate` to use sample data.
c. Run `docker-compose run --rm django ./manage.py makeclient` to create a client Application object for authentication.

d. Run `docker-compose run --rm django ./manage.py populate` to use sample data.


### Run Application
1. Run `docker-compose up`.
Expand Down
47 changes: 25 additions & 22 deletions sample_data/ingest_use_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import os
from pathlib import Path

from django.contrib.auth.models import User
from django.contrib.gis.geos import Point
from django.core.files.base import ContentFile
import requests

from uvdat.core.models import Chart, Context, Dataset, FileItem
from uvdat.core.models import Chart, Project, Dataset, FileItem

from .use_cases.boston_floods import ingest as boston_floods_ingest
from .use_cases.new_york_energy import ingest as new_york_energy_ingest
Expand Down Expand Up @@ -54,26 +55,28 @@ def ingest_file(file_info, index=0, dataset=None, chart=None):
new_file_item.file.save(file_path, ContentFile(f.read()))


def ingest_contexts(use_case):
context_file_path = USE_CASE_FOLDER / use_case / 'contexts.json'
if context_file_path.exists():
print('Creating Context objects...')
with open(context_file_path) as contexts_json:
data = json.load(contexts_json)
for context in data:
print('\t- ', context['name'])
existing = Context.objects.filter(name=context['name'])
if existing.count():
context_for_setting = existing.first()
else:
context_for_setting = Context.objects.create(
name=context['name'],
default_map_center=Point(*context['default_map_center']),
default_map_zoom=context['default_map_zoom'],
)
print('\t', f'Context {context_for_setting.name} created.')
def ingest_projects(use_case):
project_file_path = USE_CASE_FOLDER / use_case / 'projects.json'
if not project_file_path.exists():
return

print('Creating Project objects...')
with open(project_file_path) as projects_json:
data = json.load(projects_json)
for project in data:
print('\t- ', project['name'])
project_for_setting, created = Project.objects.get_or_create(
name=project['name'],
defaults={
'default_map_center': Point(*project['default_map_center']),
'default_map_zoom': project['default_map_zoom'],
},
)
if created:
print('\t', f'Project {project_for_setting.name} created.')

context_for_setting.datasets.set(Dataset.objects.filter(name__in=context['datasets']))
project_for_setting.datasets.set(Dataset.objects.filter(name__in=project['datasets']))
project_for_setting.set_permissions(owner=User.objects.filter(is_superuser=True).first())


def ingest_charts(use_case):
Expand All @@ -91,7 +94,7 @@ def ingest_charts(use_case):
new_chart = Chart.objects.create(
name=chart['name'],
description=chart['description'],
context=Context.objects.get(name=chart['context']),
project=Project.objects.get(name=chart['project']),
chart_options=chart.get('chart_options'),
metadata=chart.get('metadata'),
editable=chart.get('editable', False),
Expand Down Expand Up @@ -162,5 +165,5 @@ def ingest_use_case(use_case_name, include_large=False, dataset_indexes=None):
include_large=include_large,
dataset_indexes=dataset_indexes,
)
ingest_contexts(use_case=use_case_name)
ingest_projects(use_case=use_case_name)
ingest_charts(use_case=use_case_name)
2 changes: 1 addition & 1 deletion sample_data/use_cases/boston_floods/charts.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
"name": "Boston Harbor Daily Tide Levels",
"description": "Raw data was obtained using the NOAA CO-OPS API for Data Retrieval and reformatted in tabular form",
"context": "Boston Transportation",
"project": "Boston Transportation",
"files": [
{
"url": "https://data.kitware.com/api/v1/item/64beb508b4d956782eee8cb1/download",
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
'django-configurations[database,email]==2.5.1',
'django-extensions==3.2.3',
'django-filter==24.3',
'django-guardian==2.4.0',
'django-oauth-toolkit==2.4.0',
'djangorestframework==3.15.2',
'django-large-image==0.10.0',
Expand Down
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ passenv =
DJANGO_MINIO_STORAGE_ACCESS_KEY
DJANGO_MINIO_STORAGE_ENDPOINT
DJANGO_MINIO_STORAGE_SECRET_KEY
DJANGO_HOMEPAGE_REDIRECT_URL
extras =
dev
deps =
Expand All @@ -66,6 +67,7 @@ passenv =
DJANGO_MINIO_STORAGE_ACCESS_KEY
DJANGO_MINIO_STORAGE_ENDPOINT
DJANGO_MINIO_STORAGE_SECRET_KEY
DJANGO_HOMEPAGE_REDIRECT_URL
extras =
dev
commands =
Expand Down
12 changes: 6 additions & 6 deletions uvdat/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from uvdat.core.models import (
Chart,
Context,
Dataset,
DerivedRegion,
FileItem,
Network,
NetworkEdge,
NetworkNode,
Project,
RasterMapLayer,
SimulationResult,
SourceRegion,
Expand All @@ -17,7 +17,7 @@
)


class ContextAdmin(admin.ModelAdmin):
class ProjectAdmin(admin.ModelAdmin):
list_display = ['id', 'name']


Expand Down Expand Up @@ -72,10 +72,10 @@ def get_dataset_name(self, obj):


class DerivedRegionAdmin(admin.ModelAdmin):
list_display = ['id', 'name', 'get_context_name', 'operation', 'get_source_region_names']
list_display = ['id', 'name', 'get_project_name', 'operation', 'get_source_region_names']

def get_context_name(self, obj):
return obj.context.name
def get_project_name(self, obj):
return obj.project.name

def get_source_region_names(self, obj):
return ', '.join(r.name for r in obj.source_regions.all())
Expand Down Expand Up @@ -115,7 +115,7 @@ class SimulationResultAdmin(admin.ModelAdmin):
list_display = ['id', 'simulation_type', 'input_args']


admin.site.register(Context, ContextAdmin)
admin.site.register(Project, ProjectAdmin)
admin.site.register(Dataset, DatasetAdmin)
admin.site.register(FileItem, FileItemAdmin)
admin.site.register(Chart, ChartAdmin)
Expand Down
8 changes: 4 additions & 4 deletions uvdat/core/management/commands/load_roads.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ def add_arguments(self, parser):
type=str,
help='Target city to fetch roads from (e.g. "Boston, MA")',
)
parser.add_argument('--context_id', nargs='?', type=int, const=1)
parser.add_argument('--project_id', nargs='?', type=int, const=1)

def handle(self, *args, **kwargs):
city = kwargs['city']
context_id = kwargs['context_id']
print(f'Populating context {context_id} with roads for {city}...')
load_roads(context_id, city)
project_id = kwargs['project_id']
print(f'Populating project {project_id} with roads for {city}...')
load_roads(project_id, city)
38 changes: 38 additions & 0 deletions uvdat/core/management/commands/makeclient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import os

from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand, CommandError
from oauth2_provider.models import Application


class Command(BaseCommand):
help = 'Creates a client Application object for authentication purposes.'

def handle(self, **kwargs):
uri = os.environ.get('VUE_APP_BASE_URL')
client_id = os.environ.get('VUE_APP_OAUTH_CLIENT_ID')
if uri is None:
raise CommandError('Environment variable VUE_APP_BASE_URL is not set.')
if client_id is None:
raise CommandError('Environment variable VUE_APP_OAUTH_CLIENT_ID is not set.')

site = Site.objects.get_current() # type: ignore
site.domain = 'uvdat.demo'
site.name = 'UVDAT'
site.save()

_, created = Application.objects.get_or_create(
name='client-app',
defaults={
'redirect_uris': uri,
'client_id': client_id,
'client_type': 'public',
'authorization_grant_type': 'authorization-code',
'skip_authorization': True,
},
)
if not created:
raise CommandError(
'The client already exists. You can administer it from the admin console.'
)
self.stdout.write(self.style.SUCCESS('Client Application created.'))
80 changes: 80 additions & 0 deletions uvdat/core/migrations/0005_projects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Generated by Django 5.0.7 on 2024-09-29 16:44

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('core', '0004_files_and_networks'),
]

operations = [
migrations.RenameModel(
old_name='Context',
new_name='Project',
),
migrations.AlterModelOptions(
name='project',
options={
'permissions': [
('owner', 'Can read, write, and delete'),
('collaborator', 'Can read and write'),
('follower', 'Can read'),
]
},
),
migrations.RemoveConstraint(
model_name='derivedregion',
name='unique-derived-region-name',
),
migrations.RemoveField(
model_name='chart',
name='context',
),
migrations.RemoveField(
model_name='derivedregion',
name='context',
),
migrations.RemoveField(
model_name='simulationresult',
name='context',
),
migrations.AddField(
model_name='chart',
name='project',
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='charts',
to='core.project',
),
),
migrations.AddField(
model_name='derivedregion',
name='project',
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='derived_regions',
to='core.project',
),
),
migrations.AddField(
model_name='simulationresult',
name='project',
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='simulation_results',
to='core.project',
),
),
migrations.AddConstraint(
model_name='derivedregion',
constraint=models.UniqueConstraint(
fields=('project', 'name'), name='unique-derived-region-name'
),
),
]
4 changes: 2 additions & 2 deletions uvdat/core/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from .chart import Chart
from .context import Context
from .dataset import Dataset
from .file_item import FileItem
from .map_layers import RasterMapLayer, VectorFeature, VectorMapLayer
from .networks import Network, NetworkEdge, NetworkNode
from .project import Project
from .regions import DerivedRegion, SourceRegion
from .simulations import SimulationResult

__all__ = [
Chart,
Context,
Project,
Dataset,
FileItem,
RasterMapLayer,
Expand Down
7 changes: 2 additions & 5 deletions uvdat/core/models/chart.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
from django.db import models

from .context import Context
from .project import Project


class Chart(models.Model):
name = models.CharField(max_length=255, unique=True)
description = models.TextField(null=True, blank=True)
context = models.ForeignKey(Context, on_delete=models.CASCADE, related_name='charts')
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='charts', null=True)
metadata = models.JSONField(blank=True, null=True)

chart_data = models.JSONField(blank=True, null=True)
chart_options = models.JSONField(blank=True, null=True)
editable = models.BooleanField(default=False)

def is_in_context(self, context_id):
return self.context.id == context_id

def spawn_conversion_task(
self,
conversion_options=None,
Expand Down
11 changes: 0 additions & 11 deletions uvdat/core/models/context.py

This file was deleted.

6 changes: 0 additions & 6 deletions uvdat/core/models/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ class DatasetType(models.TextChoices):
choices=DatasetType.choices,
)

def is_in_context(self, context_id):
from uvdat.core.models import Context

context = Context.objects.get(id=context_id)
return context.datasets.filter(id=self.id).exists()

def spawn_conversion_task(
self,
style_options=None,
Expand Down
Loading

0 comments on commit 6a3c0d4

Please sign in to comment.