Compare commits

...
This repository has been archived on 2019-11-11. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

13 commits

34 changed files with 524 additions and 2 deletions

9
.gitignore vendored
View file

@ -11,9 +11,7 @@ db.sqlite3
# Environments
.python-version
.env
.venv
env/
venv/
# Distribution / packaging
@ -24,3 +22,10 @@ dist/
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# IDE stuff
.idea/
.vscode/
# Other stuff
_tmp

18
Makefile Normal file
View file

@ -0,0 +1,18 @@
# General settings
APP_NAME := tofu
PYTHON ?= python3
# Development server
SERVER_LISTEN ?= 0.0.0.0:8037
.PHONY: run
# Default target: none
all:
### Local development
# Run django development server
run:
$(PYTHON) manage.py runserver $(SERVER_LISTEN)

0
issues/__init__.py Normal file
View file

5
issues/admin.py Normal file
View file

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Issue
admin.site.register(Issue)

5
issues/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class IssuesConfig(AppConfig):
name = 'issues'

9
issues/forms.py Normal file
View file

@ -0,0 +1,9 @@
from django.forms import ModelForm
from .models import Issue
class IssueForm(ModelForm):
class Meta:
model = Issue
fields = ['project', 'title', 'text']

View file

@ -0,0 +1,27 @@
# Generated by Django 2.2.1 on 2019-05-27 12:56
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('projects', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Issue',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('text', models.TextField(blank=True)),
('create_date', models.DateTimeField(default=django.utils.timezone.now)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projects.Project')),
],
),
]

View file

14
issues/models.py Normal file
View file

@ -0,0 +1,14 @@
from django.db import models
from django.utils import timezone
from projects.models import Project
class Issue(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField(blank=True)
create_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}Issue: {{ issue.title }}{% endblock %}
{% block content %}
<h2>#{{ issue.id }}: {{ issue.title }}</h2>
<p>
<b>Project:</b> {{ issue.project }}<br/>
<b>Created:</b> {{ issue.create_date }}
</p>
<p>{{ issue.text }}</p>
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends "base.html" %}
{% block title %}Issues{% endblock %}
{% block content %}
{% if issue_list %}
<ul>
{% for issue in issue_list %}
<li>
<a href="{% url 'issues:detail' issue.id %}">{{ issue.title }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No issues.</p>
{% endif %}
<p><a href="{% url 'issues:new' %}">Create new issue</a></p>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block title %}Create issue{% endblock %}
{% block content %}
<h2>Create new issue</h2>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'issues:new' %}" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
<tr>
<td colspan="2">
<input type="submit" value="Create issue" />
</td>
</tr>
</table>
</form>
{% endblock %}

3
issues/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

10
issues/urls.py Normal file
View file

@ -0,0 +1,10 @@
from django.urls import path
from . import views
app_name = 'issues'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('new', views.new, name='new'),
path('<int:issue_id>/', views.DetailView.as_view(), name='detail'),
]

42
issues/views.py Normal file
View file

@ -0,0 +1,42 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import generic
from .forms import IssueForm
from .models import Issue
class IndexView(LoginRequiredMixin, generic.ListView):
template_name = 'issues/index.html'
context_object_name = 'issue_list'
def get_queryset(self):
return Issue.objects.order_by('create_date')
class DetailView(LoginRequiredMixin, generic.DetailView):
model = Issue
template_name = 'issues/detail.html'
pk_url_kwarg = 'issue_id'
# class NewIssueForm(LoginRequiredMixin, generic.FormView):
# template_name = 'issues/new.html'
# form_class = IssueForm
# success_url =
@login_required
def new(request):
if request.method == 'POST':
form = IssueForm(request.POST)
if form.is_valid():
new_issue = form.save()
return HttpResponseRedirect('/issues/{}'.format(new_issue.id))
else:
form = IssueForm()
return render(request, 'issues/new.html', {
'form': form,
})

15
manage.py Executable file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env python3
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tofu.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

0
projects/__init__.py Normal file
View file

5
projects/admin.py Normal file
View file

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Project
admin.site.register(Project)

5
projects/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ProjectsConfig(AppConfig):
name = 'projects'

View file

@ -0,0 +1,23 @@
# Generated by Django 2.2.1 on 2019-05-27 12:56
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Project',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('project_key', models.CharField(max_length=16, unique=True)),
('name', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
],
),
]

View file

10
projects/models.py Normal file
View file

@ -0,0 +1,10 @@
from django.db import models
class Project(models.Model):
project_key = models.CharField(max_length=16, unique=True)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
def __str__(self):
return '{} - {}'.format(self.project_key, self.name)

View file

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block title %}Projects{% endblock %}
{% block content %}
{% if project_list %}
<ul>
{% for project in project_list %}
<li>
<a href="{% url 'projects:view' project.project_key %}">{{ project.project_key }}: {{ project.name }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No projects.</p>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block title %}Project: {{ project.name }}{% endblock %}
{% block content %}
<h2>{{ project.project_key }} - {{ project.name }}</h2>
<p>{{ project.description }}</p>
<h3>Issues</h3>
{% if issue_list %}
<ul>
{% for issue in issue_list %}
<li>
<a href="{% url 'issues:detail' issue.id %}">{{ issue.title }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>This project has no issues yet.</p>
{% endif %}
{% endblock %}

3
projects/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
projects/urls.py Normal file
View file

@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = 'projects'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<slug:project_key>/', views.ProjectView.as_view(), name='view'),
]

22
projects/views.py Normal file
View file

@ -0,0 +1,22 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import generic
from .models import Project
class IndexView(LoginRequiredMixin, generic.ListView):
template_name = 'projects/index.html'
context_object_name = 'project_list'
def get_queryset(self):
return Project.objects.order_by('project_key')
class ProjectView(LoginRequiredMixin, generic.DetailView):
model = Project
template_name = 'projects/view.html'
slug_field = slug_url_kwarg = 'project_key'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['issue_list'] = self.get_object().issue_set.order_by('create_date')
return context

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
django

12
templates/base.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}Tofu{% endblock %} - Tofu</title>
</head>
<body>
<h1>Tofu</h1>
{% block content %}No content.{% endblock %}
</body>
</html>

View file

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<h2>Login</h2>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
</form>
{% endblock %}

0
tofu/__init__.py Normal file
View file

131
tofu/settings.py Normal file
View file

@ -0,0 +1,131 @@
"""
Django settings for tofu project.
Generated by 'django-admin startproject' using Django 2.1.7.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'p53=*^ls87h6@*b^-d&t!0suv4r5slw=o(%ji!v+ap_%_!lmm2'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'issues.apps.IssuesConfig',
'projects.apps.ProjectsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'tofu.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'tofu.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Berlin'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
# Other settings
LOGIN_URL = '/account/login/'
LOGIN_REDIRECT_URL = '/projects/'
LOGOUT_REDIRECT_URL = '/account/login/'

29
tofu/urls.py Normal file
View file

@ -0,0 +1,29 @@
"""tofu URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.shortcuts import redirect
from django.urls import include, path
urlpatterns = [
path('account/', include('django.contrib.auth.urls')),
path('issues/', include('issues.urls')),
path('projects/', include('projects.urls')),
path('admin/', admin.site.urls),
# TODO temporarily: just redirect / to /projects/
path('', lambda request: redirect('projects/', permanent=False)),
]

16
tofu/wsgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
WSGI config for tofu project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tofu.settings')
application = get_wsgi_application()