chore: Run formatters and fix lint errors
This commit is contained in:
parent
09ad2ca896
commit
430b3b40bb
35 changed files with 415 additions and 284 deletions
|
@ -1,11 +1,12 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
@admin.register(models.ApiKey)
|
||||
class ApiKeyAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'last_used', 'displayValue']
|
||||
readonly_fields = ['keyId', 'key', 'last_used', 'displayValue']
|
||||
list_display = ["name", "last_used", "displayValue"]
|
||||
readonly_fields = ["keyId", "key", "last_used", "displayValue"]
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if not change:
|
||||
|
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class ApiConfig(AppConfig):
|
||||
name = 'api'
|
||||
name = "api"
|
||||
|
|
|
@ -1,86 +1,93 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
''' API client for bocal-site '''
|
||||
""" API client for bocal-site """
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
import hmac
|
||||
import hashlib
|
||||
from datetime import datetime, date
|
||||
import argparse
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os.path
|
||||
import sys
|
||||
import urllib.request
|
||||
from datetime import date, datetime
|
||||
|
||||
|
||||
def sendReq(url):
|
||||
def send(payload, host):
|
||||
try:
|
||||
req = urllib.request.Request('https://{}/{}'.format(host, url),
|
||||
json.dumps(payload).encode('ascii'))
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
req = urllib.request.Request(
|
||||
"https://{}/{}".format(host, url), json.dumps(payload).encode("ascii")
|
||||
)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
handle = urllib.request.urlopen(req)
|
||||
code = handle.getcode()
|
||||
content = handle.read()
|
||||
handle.close()
|
||||
return (code, content.decode('utf-8'))
|
||||
return (code, content.decode("utf-8"))
|
||||
except urllib.error.HTTPError as e:
|
||||
return (e.code, e.read().decode('utf-8'))
|
||||
return (e.code, e.read().decode("utf-8"))
|
||||
|
||||
def authentify(apiKey, payload):
|
||||
keyId, key = apiKey.split('$')
|
||||
keyId, key = apiKey.split("$")
|
||||
keyId = int(keyId)
|
||||
time = datetime.now().timestamp()
|
||||
mac = hmac.new(key.encode('utf-8'),
|
||||
msg=str(int(time)).encode('utf-8'),
|
||||
digestmod=hashlib.sha256)
|
||||
mac = hmac.new(
|
||||
key.encode("utf-8"),
|
||||
msg=str(int(time)).encode("utf-8"),
|
||||
digestmod=hashlib.sha256,
|
||||
)
|
||||
payload_enc = json.dumps(payload)
|
||||
mac.update(payload_enc.encode('utf-8'))
|
||||
mac.update(payload_enc.encode("utf-8"))
|
||||
|
||||
auth = {
|
||||
'keyId': keyId,
|
||||
'timestamp': time,
|
||||
'hmac': mac.hexdigest(),
|
||||
"keyId": keyId,
|
||||
"timestamp": time,
|
||||
"hmac": mac.hexdigest(),
|
||||
}
|
||||
|
||||
return {
|
||||
'auth': auth,
|
||||
'req': payload_enc,
|
||||
"auth": auth,
|
||||
"req": payload_enc,
|
||||
}
|
||||
|
||||
def decorator(fct):
|
||||
''' Decorator. Adds authentication layer. '''
|
||||
"""Decorator. Adds authentication layer."""
|
||||
|
||||
def wrap(host, apiKey, *args, **kwargs):
|
||||
innerReq = fct(*args, **kwargs)
|
||||
payload = authentify(apiKey, innerReq)
|
||||
return send(payload, host)
|
||||
|
||||
return wrap
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@sendReq(url='api/publish')
|
||||
@sendReq(url="api/publish")
|
||||
def publish(bocId, url, date):
|
||||
return {
|
||||
'id': bocId,
|
||||
'url': url,
|
||||
'date': date.strftime('%Y-%m-%d'),
|
||||
"id": bocId,
|
||||
"url": url,
|
||||
"date": date.strftime("%Y-%m-%d"),
|
||||
}
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
TOKEN_DFT_FILE = os.path.expanduser("~/.bocal_api_token")
|
||||
DFT_HOST = 'bocal.cof.ens.fr'
|
||||
DFT_HOST = "bocal.cof.ens.fr"
|
||||
|
||||
|
||||
def read_token(path):
|
||||
token = ''
|
||||
token = ""
|
||||
try:
|
||||
with open(path, 'r') as handle:
|
||||
with open(path, "r") as handle:
|
||||
token = handle.readline().strip()
|
||||
except FileNotFoundError:
|
||||
print("[Erreur] Fichier d'identifiants absent (`{}`).".format(path),
|
||||
file=sys.stderr)
|
||||
print(
|
||||
"[Erreur] Fichier d'identifiants absent (`{}`).".format(path),
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
return token
|
||||
|
||||
|
@ -89,6 +96,7 @@ def cmd(func):
|
|||
def wrap(parse_args, *args, **kwargs):
|
||||
token = read_token(parse_args.creds)
|
||||
return func(token, parse_args, *args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
|
@ -97,13 +105,9 @@ def cmd_publish(token, args):
|
|||
if not args.date:
|
||||
publish_date = date.today()
|
||||
else:
|
||||
year, month, day = [int(x) for x in args.date.strip().split('-')]
|
||||
year, month, day = [int(x) for x in args.date.strip().split("-")]
|
||||
publish_date = date(year=year, month=month, day=day)
|
||||
(ret_code, ret_str) = publish(args.host,
|
||||
token,
|
||||
args.numero,
|
||||
args.url,
|
||||
publish_date)
|
||||
(ret_code, ret_str) = publish(args.host, token, args.numero, args.url, publish_date)
|
||||
if ret_code == 200:
|
||||
print("Succès :)")
|
||||
else:
|
||||
|
@ -113,29 +117,30 @@ def cmd_publish(token, args):
|
|||
|
||||
def setup_argparse():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--host',
|
||||
help=("Adresse du site à contacter (par défaut, "
|
||||
"`{}`).".format(DFT_HOST)))
|
||||
parser.add_argument('--creds',
|
||||
help=("Fichier contenant le token API à utiliser "
|
||||
"(par défaut, `{}`)".format(TOKEN_DFT_FILE)))
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
help=("Adresse du site à contacter (par défaut, " "`{}`).".format(DFT_HOST)),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--creds",
|
||||
help=(
|
||||
"Fichier contenant le token API à utiliser "
|
||||
"(par défaut, `{}`)".format(TOKEN_DFT_FILE)
|
||||
),
|
||||
)
|
||||
parser.set_defaults(creds=TOKEN_DFT_FILE, host=DFT_HOST)
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
parser_publish = subparsers.add_parser('publier',
|
||||
help='Publier un numéro du BOcal')
|
||||
parser_publish.add_argument('numero',
|
||||
help='Numéro du BOcal')
|
||||
parser_publish.add_argument('url',
|
||||
help='Adresse (locale) du PDF du BOcal')
|
||||
parser_publish.add_argument('-d', '--date',
|
||||
help="Date de publication indiquée")
|
||||
parser_publish = subparsers.add_parser("publier", help="Publier un numéro du BOcal")
|
||||
parser_publish.add_argument("numero", help="Numéro du BOcal")
|
||||
parser_publish.add_argument("url", help="Adresse (locale) du PDF du BOcal")
|
||||
parser_publish.add_argument("-d", "--date", help="Date de publication indiquée")
|
||||
parser_publish.set_defaults(func=cmd_publish)
|
||||
|
||||
out_args = parser.parse_args()
|
||||
if 'func' not in out_args: # No subcommand provided
|
||||
if "func" not in out_args: # No subcommand provided
|
||||
print("You must provide a command.", file=sys.stderr)
|
||||
print(parser.parse_args(['-h']), file=sys.stderr)
|
||||
print(parser.parse_args(["-h"]), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return out_args
|
||||
|
||||
|
@ -145,5 +150,5 @@ def main():
|
|||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
@ -10,17 +11,37 @@ class Migration(migrations.Migration):
|
|||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ApiKey',
|
||||
name="ApiKey",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(max_length=128, verbose_name='API key')),
|
||||
('name', models.CharField(help_text='Where is this key used from?', max_length=256, verbose_name='Key name')),
|
||||
('last_used', models.DateTimeField(default=datetime.datetime(1970, 1, 1, 1, 0), verbose_name='Last used')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("key", models.CharField(max_length=128, verbose_name="API key")),
|
||||
(
|
||||
"name",
|
||||
models.CharField(
|
||||
help_text="Where is this key used from?",
|
||||
max_length=256,
|
||||
verbose_name="Key name",
|
||||
),
|
||||
),
|
||||
(
|
||||
"last_used",
|
||||
models.DateTimeField(
|
||||
default=datetime.datetime(1970, 1, 1, 1, 0),
|
||||
verbose_name="Last used",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
from django.db import models
|
||||
from datetime import datetime, timedelta
|
||||
import hmac
|
||||
import hashlib
|
||||
import hmac
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class ApiKey(models.Model):
|
||||
''' An API key, to login using the API
|
||||
"""An API key, to login using the API
|
||||
|
||||
An API key consists in a somewhat long chunk of ascii text, *not*
|
||||
containing any dollar ($) sign. It is saved on the client's machine as
|
||||
a string "keyId$key".
|
||||
An API token (to authentify a request) is a triplet (ts, kid, hmac) of
|
||||
a timestamp `ts`, the key id `kid` and
|
||||
hmac = `HMAC(key, ts + data, sha256)`
|
||||
where `data` is the normalized (`json.dumps(json.loads(...))`) value of
|
||||
the data part of the request.
|
||||
'''
|
||||
key = models.CharField("API key",
|
||||
max_length=128)
|
||||
name = models.CharField("Key name",
|
||||
max_length=256,
|
||||
help_text="Where is this key used from?")
|
||||
last_used = models.DateTimeField("Last used",
|
||||
default=datetime.fromtimestamp(0))
|
||||
An API key consists in a somewhat long chunk of ascii text, *not*
|
||||
containing any dollar ($) sign. It is saved on the client's machine as
|
||||
a string "keyId$key".
|
||||
An API token (to authentify a request) is a triplet (ts, kid, hmac) of
|
||||
a timestamp `ts`, the key id `kid` and
|
||||
hmac = `HMAC(key, ts + data, sha256)`
|
||||
where `data` is the normalized (`json.dumps(json.loads(...))`) value of
|
||||
the data part of the request.
|
||||
"""
|
||||
|
||||
key = models.CharField("API key", max_length=128)
|
||||
name = models.CharField(
|
||||
"Key name", max_length=256, help_text="Where is this key used from?"
|
||||
)
|
||||
last_used = models.DateTimeField("Last used", default=datetime.fromtimestamp(0))
|
||||
|
||||
def everUsed(self):
|
||||
return self.last_used > datetime.fromtimestamp(0)
|
||||
|
@ -33,8 +33,7 @@ class ApiKey(models.Model):
|
|||
if not self.key:
|
||||
KEY_SIZE = 64
|
||||
KEY_CHARS = string.ascii_letters + string.digits
|
||||
self.key = ''.join(
|
||||
[random.choice(KEY_CHARS) for _ in range(KEY_SIZE)])
|
||||
self.key = "".join([random.choice(KEY_CHARS) for _ in range(KEY_SIZE)])
|
||||
|
||||
@property
|
||||
def keyId(self):
|
||||
|
@ -52,9 +51,11 @@ class ApiKey(models.Model):
|
|||
if datetime.now() - timedelta(minutes=5) > claimedDate:
|
||||
return False
|
||||
|
||||
mac = hmac.new(self.key.encode('utf-8'),
|
||||
msg=str(int(claimedDate.timestamp())).encode('utf-8'),
|
||||
digestmod=hashlib.sha256)
|
||||
mac.update(data.encode('utf-8'))
|
||||
mac = hmac.new(
|
||||
self.key.encode("utf-8"),
|
||||
msg=str(int(claimedDate.timestamp())).encode("utf-8"),
|
||||
digestmod=hashlib.sha256,
|
||||
)
|
||||
mac.update(data.encode("utf-8"))
|
||||
|
||||
return hmac.compare_digest(mac.hexdigest(), inpMac)
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'manisite'
|
||||
app_name = "manisite"
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^publish$', views.publishApiView),
|
||||
path("publish", views.publishApiView),
|
||||
]
|
||||
|
|
56
api/views.py
56
api/views.py
|
@ -1,27 +1,30 @@
|
|||
from django.http import response
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
import json
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import response
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
import mainsite.models as mainModels
|
||||
|
||||
from . import models
|
||||
import mainsite.models as mainModels
|
||||
|
||||
|
||||
def authentify(data, payload):
|
||||
''' returns whether the request's authentification is correct '''
|
||||
required = ['keyId', 'timestamp', 'hmac']
|
||||
"""returns whether the request's authentification is correct"""
|
||||
required = ["keyId", "timestamp", "hmac"]
|
||||
for field in required:
|
||||
if field not in data:
|
||||
return response.HttpResponseForbidden(
|
||||
'Missing required field "{}"'.format(field))
|
||||
'Missing required field "{}"'.format(field)
|
||||
)
|
||||
try:
|
||||
key = models.ApiKey.objects.get(id=data['keyId'])
|
||||
key = models.ApiKey.objects.get(id=data["keyId"])
|
||||
except models.ApiKey.DoesNotExist:
|
||||
return response.HttpResponseForbidden('Bad authentication')
|
||||
return response.HttpResponseForbidden("Bad authentication")
|
||||
|
||||
if not key.isCorrect(data['timestamp'], data['hmac'], payload):
|
||||
return response.HttpResponseForbidden('Bad authentication')
|
||||
if not key.isCorrect(data["timestamp"], data["hmac"], payload):
|
||||
return response.HttpResponseForbidden("Bad authentication")
|
||||
|
||||
|
||||
def apiView(required=[]):
|
||||
|
@ -29,15 +32,15 @@ def apiView(required=[]):
|
|||
@csrf_exempt
|
||||
def wrap(request, *args, **kwargs):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
data = json.loads(request.body.decode("utf-8"))
|
||||
except TypeError:
|
||||
return response.HttpResponseBadRequest("Bad packet format")
|
||||
except json.decoder.JSONDecodeError:
|
||||
return response.HttpResponseBadRequest("Bad json")
|
||||
|
||||
try:
|
||||
authData = data['auth']
|
||||
reqDataOrig = data['req']
|
||||
authData = data["auth"]
|
||||
reqDataOrig = data["req"]
|
||||
except KeyError:
|
||||
return response.HttpResponseBadRequest("Bad request format")
|
||||
|
||||
|
@ -51,39 +54,40 @@ def apiView(required=[]):
|
|||
for field in required:
|
||||
if field not in reqData:
|
||||
return response.HttpResponseBadRequest(
|
||||
"Missing field {}".format(field))
|
||||
"Missing field {}".format(field)
|
||||
)
|
||||
|
||||
authVal = authentify(authData, reqDataOrig)
|
||||
if authVal is not None:
|
||||
return authVal
|
||||
|
||||
return fct(request, reqData, *args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@apiView(required=["id", "url", "date"])
|
||||
def publishApiView(request, data):
|
||||
''' Publish a BOcal, and create the corresponding year if needed '''
|
||||
if mainModels.Publication.objects.filter(num=data['id']).count() > 0:
|
||||
"""Publish a BOcal, and create the corresponding year if needed"""
|
||||
if mainModels.Publication.objects.filter(num=data["id"]).count() > 0:
|
||||
return response.HttpResponseBadRequest(
|
||||
"Un BOcal du même numéro est déjà présent ! Ajoutez celui-ci à la "
|
||||
"main si vous voulez vraiment faire ça.")
|
||||
"main si vous voulez vraiment faire ça."
|
||||
)
|
||||
|
||||
try:
|
||||
year, month, day = [int(x) for x in data['date'].split('-')]
|
||||
year, month, day = [int(x) for x in data["date"].split("-")]
|
||||
date = datetime.date(year, month, day)
|
||||
except:
|
||||
except Exception:
|
||||
return response.HttpResponseBadRequest("Bad date")
|
||||
|
||||
pub = mainModels.Publication(num=data['id'],
|
||||
url=data['url'],
|
||||
date=date)
|
||||
pub = mainModels.Publication(num=data["id"], url=data["url"], date=date)
|
||||
try:
|
||||
pub.full_clean()
|
||||
except ValidationError as e:
|
||||
return response.HttpResponseBadRequest(
|
||||
"Invalid data: {}".format(e))
|
||||
return response.HttpResponseBadRequest("Invalid data: {}".format(e))
|
||||
pub.save()
|
||||
|
||||
pub.createPubYear()
|
||||
|
|
|
@ -1 +1 @@
|
|||
default_app_config = 'bocal_auth.apps.BocalAuthConfig'
|
||||
default_app_config = "bocal_auth.apps.BocalAuthConfig"
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class BocalAuthConfig(AppConfig):
|
||||
name = 'bocal_auth'
|
||||
name = "bocal_auth"
|
||||
|
||||
def ready(self):
|
||||
from . import signals
|
||||
pass
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django_cas_ng.backends import CASBackend
|
||||
|
||||
from .models import CasUser
|
||||
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
# Generated by Django 1.11.5 on 2017-10-14 16:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -12,14 +12,22 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
("auth", "0008_alter_user_username_max_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CasUser',
|
||||
name="CasUser",
|
||||
fields=[
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"user",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
|
||||
|
||||
class CasUser(models.Model):
|
||||
''' Describes a Django user that was created through CAS '''
|
||||
"""Describes a Django user that was created through CAS"""
|
||||
|
||||
user = models.OneToOneField(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True)
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
''' Reads a .rhosts file '''
|
||||
""" Reads a .rhosts file """
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from .models import CasUser
|
||||
|
||||
|
||||
def hasUser(user, allowed_domains=[]):
|
||||
''' Check that `user` appears in the rhosts file.
|
||||
"""Check that `user` appears in the rhosts file.
|
||||
If `allowed_domains` is not empty, also checks that the user belongs to one
|
||||
of the specified domains. '''
|
||||
of the specified domains."""
|
||||
|
||||
def clearLine(line):
|
||||
line = line.strip()
|
||||
hashPos = line.find('#')
|
||||
hashPos = line.find("#")
|
||||
if hashPos >= 0:
|
||||
line = line[:hashPos]
|
||||
return line
|
||||
|
||||
with open(settings.RHOSTS_PATH, 'r') as handle:
|
||||
with open(settings.RHOSTS_PATH, "r") as handle:
|
||||
for line in handle:
|
||||
line = clearLine(line)
|
||||
if not line:
|
||||
|
@ -31,7 +32,7 @@ def hasUser(user, allowed_domains=[]):
|
|||
if login != user: # Not the ones we're looking for
|
||||
continue
|
||||
|
||||
if domain[:2] != '+@': # Not a valid domain
|
||||
if domain[:2] != "+@": # Not a valid domain
|
||||
continue
|
||||
domain = domain[2:]
|
||||
|
||||
|
@ -43,16 +44,16 @@ def hasUser(user, allowed_domains=[]):
|
|||
|
||||
|
||||
def default_allowed(user):
|
||||
return hasUser(user, allowed_domains=['eleves'])
|
||||
return hasUser(user, allowed_domains=["eleves"])
|
||||
|
||||
|
||||
class NoBOcalException(Exception):
|
||||
def __str__():
|
||||
def __str__(self):
|
||||
return "The BOcal group was not created"
|
||||
|
||||
|
||||
def bocalGroup():
|
||||
qs = Group.objects.filter(name='BOcal')
|
||||
qs = Group.objects.filter(name="BOcal")
|
||||
if qs.count() != 1:
|
||||
raise NoBOcalException
|
||||
return qs[0]
|
||||
|
@ -80,6 +81,7 @@ def requireCasUser(fct):
|
|||
if not hasCas(user):
|
||||
return
|
||||
return fct(user, *args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
|
@ -100,4 +102,5 @@ def forceReevalRhosts(fct):
|
|||
def wrap(req, *args, **kwargs):
|
||||
evalRhostsPrivileges(req.user)
|
||||
return fct(req, *args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
from django.dispatch import receiver
|
||||
from django_cas_ng.signals import cas_user_authenticated, cas_user_logout
|
||||
|
||||
from . import rhosts
|
||||
|
||||
|
||||
@receiver(cas_user_authenticated)
|
||||
def onCasLogin(sender, user, **kwargs):
|
||||
''' Called upon login of a user through CAS '''
|
||||
"""Called upon login of a user through CAS"""
|
||||
rhosts.evalRhostsPrivileges(user)
|
||||
|
||||
|
||||
@receiver(cas_user_logout)
|
||||
def onCasLogout(sender, user, **kwargs):
|
||||
''' Strip the user from their privileges — in case something goes wrong
|
||||
during the next authentication '''
|
||||
"""Strip the user from their privileges — in case something goes wrong
|
||||
during the next authentication"""
|
||||
rhosts.logout(user)
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from django.shortcuts import render, redirect
|
||||
from django.urls import reverse
|
||||
from urllib.parse import quote as urlquote
|
||||
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from urllib.parse import quote as urlquote
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
def login(req):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from solo.admin import SingletonModelAdmin
|
||||
from . import models
|
||||
|
||||
from . import models
|
||||
|
||||
admin.site.register(models.SiteConfiguration, SingletonModelAdmin)
|
||||
admin.site.register(models.Publication)
|
||||
|
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class MainsiteConfig(AppConfig):
|
||||
name = 'mainsite'
|
||||
name = "mainsite"
|
||||
|
|
|
@ -7,10 +7,11 @@ def sidebar_years(req):
|
|||
avail_years = models.PublicationYear.objects.all()
|
||||
publi_years = [year for year in avail_years if year.publis().count() > 0]
|
||||
|
||||
num_special_publications = models.Publication.objects\
|
||||
.filter(is_special=True).count()
|
||||
num_special_publications = models.Publication.objects.filter(
|
||||
is_special=True
|
||||
).count()
|
||||
|
||||
return {
|
||||
'publication_years': publi_years,
|
||||
'has_special_publications': num_special_publications > 0,
|
||||
"publication_years": publi_years,
|
||||
"has_special_publications": num_special_publications > 0,
|
||||
}
|
||||
|
|
|
@ -9,20 +9,57 @@ class Migration(migrations.Migration):
|
|||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Publication',
|
||||
name="Publication",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('num', models.CharField(max_length=128, verbose_name='Numéro du BOcal')),
|
||||
('url', models.CharField(max_length=512, verbose_name='Adresse sur le site')),
|
||||
('date', models.DateField(verbose_name='Publication')),
|
||||
('is_special', models.BooleanField(default=False, help_text='Numéro du BOcal non-numéroté', verbose_name='Numéro spécial')),
|
||||
('descr', models.CharField(blank=True, max_length=512, verbose_name='Description (optionnelle)')),
|
||||
('custom_name', models.CharField(blank=True, help_text='Vide pour laisser le numéro seulement', max_length=128, verbose_name='Nom customisé')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"num",
|
||||
models.CharField(max_length=128, verbose_name="Numéro du BOcal"),
|
||||
),
|
||||
(
|
||||
"url",
|
||||
models.CharField(
|
||||
max_length=512, verbose_name="Adresse sur le site"
|
||||
),
|
||||
),
|
||||
("date", models.DateField(verbose_name="Publication")),
|
||||
(
|
||||
"is_special",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Numéro du BOcal non-numéroté",
|
||||
verbose_name="Numéro spécial",
|
||||
),
|
||||
),
|
||||
(
|
||||
"descr",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=512,
|
||||
verbose_name="Description (optionnelle)",
|
||||
),
|
||||
),
|
||||
(
|
||||
"custom_name",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Vide pour laisser le numéro seulement",
|
||||
max_length=128,
|
||||
verbose_name="Nom customisé",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -8,19 +8,27 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainsite', '0001_initial'),
|
||||
("mainsite", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PublicationYear',
|
||||
name="PublicationYear",
|
||||
fields=[
|
||||
('startYear', models.IntegerField(help_text='Année scolaire à partir du 1/08', primary_key=True, serialize=False, verbose_name='Année de début')),
|
||||
('descr', models.TextField(verbose_name="Accroche de l'année")),
|
||||
(
|
||||
"startYear",
|
||||
models.IntegerField(
|
||||
help_text="Année scolaire à partir du 1/08",
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="Année de début",
|
||||
),
|
||||
),
|
||||
("descr", models.TextField(verbose_name="Accroche de l'année")),
|
||||
],
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='publication',
|
||||
options={'ordering': ['date']},
|
||||
name="publication",
|
||||
options={"ordering": ["date"]},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -8,20 +8,41 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainsite', '0002_auto_20170922_1438'),
|
||||
("mainsite", "0002_auto_20170922_1438"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SiteConfiguration',
|
||||
name="SiteConfiguration",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('homepageText', models.TextField(verbose_name="Texte de la page d'accueil (HTML)")),
|
||||
('writearticleText', models.TextField(verbose_name='Texte de la page « écrire » (HTML)')),
|
||||
('email', models.CharField(help_text='Attention au spam…', max_length=128, verbose_name='Adresse de contact du BOcal')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"homepageText",
|
||||
models.TextField(verbose_name="Texte de la page d'accueil (HTML)"),
|
||||
),
|
||||
(
|
||||
"writearticleText",
|
||||
models.TextField(verbose_name="Texte de la page « écrire » (HTML)"),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.CharField(
|
||||
help_text="Attention au spam…",
|
||||
max_length=128,
|
||||
verbose_name="Adresse de contact du BOcal",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Configuration du site',
|
||||
"verbose_name": "Configuration du site",
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -8,13 +8,17 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainsite', '0003_siteconfiguration'),
|
||||
("mainsite", "0003_siteconfiguration"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='publication',
|
||||
name='in_year_view_anyway',
|
||||
field=models.BooleanField(default=False, help_text="Si le numéro est spécial, l'afficher quand même dans la page de l'année correspondante.", verbose_name="Aussi dans l'année"),
|
||||
model_name="publication",
|
||||
name="in_year_view_anyway",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Si le numéro est spécial, l'afficher quand même dans la page de l'année correspondante.",
|
||||
verbose_name="Aussi dans l'année",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -8,12 +8,12 @@ from django.db import migrations
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainsite', '0004_publication_in_year_view_anyway'),
|
||||
("mainsite", "0004_publication_in_year_view_anyway"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='publicationyear',
|
||||
options={'ordering': ['-startYear']},
|
||||
name="publicationyear",
|
||||
options={"ordering": ["-startYear"]},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -2,30 +2,36 @@
|
|||
# Generated by Django 1.11.5 on 2017-09-23 16:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainsite', '0005_auto_20170922_1916'),
|
||||
("mainsite", "0005_auto_20170922_1916"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='publicationyear',
|
||||
name='descr',
|
||||
field=markdownx.models.MarkdownxField(verbose_name="Accroche de l'année (Markdown)"),
|
||||
model_name="publicationyear",
|
||||
name="descr",
|
||||
field=markdownx.models.MarkdownxField(
|
||||
verbose_name="Accroche de l'année (Markdown)"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='siteconfiguration',
|
||||
name='homepageText',
|
||||
field=markdownx.models.MarkdownxField(verbose_name="Texte de la page d'accueil (Markdown)"),
|
||||
model_name="siteconfiguration",
|
||||
name="homepageText",
|
||||
field=markdownx.models.MarkdownxField(
|
||||
verbose_name="Texte de la page d'accueil (Markdown)"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='siteconfiguration',
|
||||
name='writearticleText',
|
||||
field=markdownx.models.MarkdownxField(verbose_name='Texte de la page « écrire » (Markdown)'),
|
||||
model_name="siteconfiguration",
|
||||
name="writearticleText",
|
||||
field=markdownx.models.MarkdownxField(
|
||||
verbose_name="Texte de la page « écrire » (Markdown)"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -2,21 +2,24 @@
|
|||
# Generated by Django 1.11.5 on 2017-09-23 16:54
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainsite', '0006_auto_20170923_1847'),
|
||||
("mainsite", "0006_auto_20170923_1847"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='siteconfiguration',
|
||||
name='specialPublisDescr',
|
||||
field=markdownx.models.MarkdownxField(default='', verbose_name='Texte de la page des publications spéciales (Markdown)'),
|
||||
model_name="siteconfiguration",
|
||||
name="specialPublisDescr",
|
||||
field=markdownx.models.MarkdownxField(
|
||||
default="",
|
||||
verbose_name="Texte de la page des publications spéciales (Markdown)",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -8,13 +8,17 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainsite', '0007_siteconfiguration_specialpublisdescr'),
|
||||
("mainsite", "0007_siteconfiguration_specialpublisdescr"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='publication',
|
||||
name='unknown_date',
|
||||
field=models.BooleanField(default=False, help_text="La date de publication du BOcal est inconnue parce qu'il est trop vieux. La date indiquée ne servira qu'à le ranger dans une année et à ordonner les BOcals.", verbose_name='Date inconnue'),
|
||||
model_name="publication",
|
||||
name="unknown_date",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="La date de publication du BOcal est inconnue parce qu'il est trop vieux. La date indiquée ne servira qu'à le ranger dans une année et à ordonner les BOcals.",
|
||||
verbose_name="Date inconnue",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.db.models import DateField, \
|
||||
CharField, \
|
||||
BooleanField, \
|
||||
IntegerField
|
||||
from solo.models import SingletonModel
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import BooleanField, CharField, DateField, IntegerField, Q
|
||||
from markdownx.models import MarkdownxField
|
||||
from solo.models import SingletonModel
|
||||
|
||||
|
||||
class SiteConfiguration(SingletonModel):
|
||||
homepageText = MarkdownxField("Texte de la page d'accueil (Markdown)")
|
||||
writearticleText = MarkdownxField("Texte de la page « écrire » (Markdown)")
|
||||
email = CharField("Adresse de contact du BOcal",
|
||||
max_length=128,
|
||||
help_text="Attention au spam…")
|
||||
specialPublisDescr = MarkdownxField("Texte de la page des "
|
||||
"publications spéciales (Markdown)")
|
||||
email = CharField(
|
||||
"Adresse de contact du BOcal", max_length=128, help_text="Attention au spam…"
|
||||
)
|
||||
specialPublisDescr = MarkdownxField(
|
||||
"Texte de la page des " "publications spéciales (Markdown)"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "Configuration du site"
|
||||
|
@ -27,33 +24,40 @@ class SiteConfiguration(SingletonModel):
|
|||
|
||||
|
||||
class Publication(models.Model):
|
||||
num = CharField('Numéro du BOcal', max_length=128)
|
||||
url = CharField('Adresse sur le site', max_length=512)
|
||||
num = CharField("Numéro du BOcal", max_length=128)
|
||||
url = CharField("Adresse sur le site", max_length=512)
|
||||
# ^ This is not a URLField because we need internal URLS, eg `/static/blah`
|
||||
|
||||
date = DateField('Publication')
|
||||
unknown_date = BooleanField('Date inconnue',
|
||||
help_text=("La date de publication du BOcal "
|
||||
"est inconnue parce qu'il est "
|
||||
"trop vieux. La date indiquée ne "
|
||||
"servira qu'à le ranger dans une "
|
||||
"année et à ordonner les BOcals."),
|
||||
default=False)
|
||||
is_special = BooleanField('Numéro spécial',
|
||||
help_text='Numéro du BOcal non-numéroté',
|
||||
default=False)
|
||||
date = DateField("Publication")
|
||||
unknown_date = BooleanField(
|
||||
"Date inconnue",
|
||||
help_text=(
|
||||
"La date de publication du BOcal "
|
||||
"est inconnue parce qu'il est "
|
||||
"trop vieux. La date indiquée ne "
|
||||
"servira qu'à le ranger dans une "
|
||||
"année et à ordonner les BOcals."
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
is_special = BooleanField(
|
||||
"Numéro spécial", help_text="Numéro du BOcal non-numéroté", default=False
|
||||
)
|
||||
in_year_view_anyway = BooleanField(
|
||||
"Aussi dans l'année",
|
||||
help_text=("Si le numéro est spécial, l'afficher quand même dans la "
|
||||
"page de l'année correspondante."),
|
||||
default=False)
|
||||
descr = CharField('Description (optionnelle)',
|
||||
max_length=512,
|
||||
blank=True)
|
||||
custom_name = CharField('Nom customisé',
|
||||
help_text='Vide pour laisser le numéro seulement',
|
||||
max_length=128,
|
||||
blank=True)
|
||||
help_text=(
|
||||
"Si le numéro est spécial, l'afficher quand même dans la "
|
||||
"page de l'année correspondante."
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
descr = CharField("Description (optionnelle)", max_length=512, blank=True)
|
||||
custom_name = CharField(
|
||||
"Nom customisé",
|
||||
help_text="Vide pour laisser le numéro seulement",
|
||||
max_length=128,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class NoPublicationYear(Exception):
|
||||
def __str__(self):
|
||||
|
@ -67,8 +71,8 @@ class Publication(models.Model):
|
|||
return startYear
|
||||
|
||||
def publicationYear(self):
|
||||
''' Fetch corresponding publication year
|
||||
Raise `NoPublicationYear` if there is no such entry '''
|
||||
"""Fetch corresponding publication year
|
||||
Raise `NoPublicationYear` if there is no such entry"""
|
||||
startYear = self.numericPublicationYear
|
||||
try:
|
||||
return PublicationYear.objects.get(startYear=startYear)
|
||||
|
@ -76,11 +80,13 @@ class Publication(models.Model):
|
|||
raise self.NoPublicationYear
|
||||
|
||||
def createPubYear(self):
|
||||
''' Creates the corresponding publication year if needed. '''
|
||||
if (PublicationYear.objects
|
||||
.filter(startYear=self.numericPublicationYear).count()) == 0:
|
||||
pubYear = PublicationYear(startYear=self.numericPublicationYear,
|
||||
descr='')
|
||||
"""Creates the corresponding publication year if needed."""
|
||||
if (
|
||||
PublicationYear.objects.filter(
|
||||
startYear=self.numericPublicationYear
|
||||
).count()
|
||||
) == 0:
|
||||
pubYear = PublicationYear(startYear=self.numericPublicationYear, descr="")
|
||||
pubYear.save()
|
||||
return True
|
||||
return False
|
||||
|
@ -90,50 +96,51 @@ class Publication(models.Model):
|
|||
return self.custom_name
|
||||
elif self.is_special:
|
||||
return self.num
|
||||
return 'BOcal n°{}'.format(self.num)
|
||||
return "BOcal n°{}".format(self.num)
|
||||
|
||||
@staticmethod
|
||||
def latest():
|
||||
return Publication.objects.order_by('-date')[0]
|
||||
return Publication.objects.order_by("-date")[0]
|
||||
|
||||
class Meta:
|
||||
ordering = ['date']
|
||||
ordering = ["date"]
|
||||
|
||||
|
||||
class PublicationYear(models.Model):
|
||||
startYear = IntegerField('Année de début',
|
||||
help_text='Année scolaire à partir du 15/08',
|
||||
primary_key=True)
|
||||
startYear = IntegerField(
|
||||
"Année de début", help_text="Année scolaire à partir du 15/08", primary_key=True
|
||||
)
|
||||
descr = MarkdownxField("Accroche de l'année (Markdown)")
|
||||
|
||||
def __str__(self):
|
||||
return '{}-{}'.format(self.startYear, self.startYear+1)
|
||||
return "{}-{}".format(self.startYear, self.startYear + 1)
|
||||
|
||||
def beg(self):
|
||||
''' First day of this publication year (incl.) '''
|
||||
"""First day of this publication year (incl.)"""
|
||||
return datetime.date(self.startYear, 8, 15)
|
||||
|
||||
def end(self):
|
||||
''' Last day of this publication year (excl.) '''
|
||||
"""Last day of this publication year (excl.)"""
|
||||
return datetime.date(self.startYear + 1, 8, 15)
|
||||
|
||||
def inYear(self, date):
|
||||
return self.beg() <= date < self.end()
|
||||
|
||||
def publis(self):
|
||||
''' List of publications from this year '''
|
||||
"""List of publications from this year"""
|
||||
return Publication.objects.filter(
|
||||
Q(is_special=False) | Q(in_year_view_anyway=True),
|
||||
date__gte=self.beg(),
|
||||
date__lt=self.end())
|
||||
date__lt=self.end(),
|
||||
)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return '/{}/'.format(self)
|
||||
return "/{}/".format(self)
|
||||
|
||||
@property
|
||||
def prettyName(self):
|
||||
return '{} – {}'.format(self.startYear, self.startYear+1)
|
||||
return "{} – {}".format(self.startYear, self.startYear + 1)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-startYear']
|
||||
ordering = ["-startYear"]
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.views.generic import TemplateView
|
||||
from django.http import Http404, HttpResponse
|
||||
|
||||
from mainsite.models import Publication, PublicationYear, SiteConfiguration
|
||||
|
||||
|
||||
def robots_view(request):
|
||||
""" Robots.txt view """
|
||||
"""Robots.txt view"""
|
||||
body = "User-Agent: *\nDisallow: /\nAllow: /$\n"
|
||||
return HttpResponse(body, content_type="text/plain")
|
||||
|
||||
|
||||
class HomeView(TemplateView):
|
||||
""" Website's homepage """
|
||||
"""Website's homepage"""
|
||||
|
||||
template_name = "mainsite/homepage.html"
|
||||
|
||||
|
||||
class WriteArticleView(TemplateView):
|
||||
""" Tell the readers how they can contribute to the BOcal """
|
||||
"""Tell the readers how they can contribute to the BOcal"""
|
||||
|
||||
template_name = "mainsite/write_article.html"
|
||||
|
||||
|
||||
class PublicationListView(TemplateView):
|
||||
""" Display a list of publications (generic class).
|
||||
"""Display a list of publications (generic class).
|
||||
|
||||
Reimplement `get_publications` (called with the url template args in a
|
||||
place where you can Http404) in subclasses to get it working. """
|
||||
place where you can Http404) in subclasses to get it working."""
|
||||
|
||||
template_name = "mainsite/publications_list_view.html"
|
||||
|
||||
def initView(self):
|
||||
""" Cannot be __init__, we don't have **kwargs there """
|
||||
"""Cannot be __init__, we don't have **kwargs there"""
|
||||
pass
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -51,7 +51,7 @@ class PublicationListView(TemplateView):
|
|||
|
||||
|
||||
class YearView(PublicationListView):
|
||||
""" Display a year worth of BOcals """
|
||||
"""Display a year worth of BOcals"""
|
||||
|
||||
def initView(self, year, nYear):
|
||||
try:
|
||||
|
@ -76,7 +76,7 @@ class YearView(PublicationListView):
|
|||
|
||||
|
||||
class SpecialPublicationsView(PublicationListView):
|
||||
""" Display the list of special publications """
|
||||
"""Display the list of special publications"""
|
||||
|
||||
def additional_context(self):
|
||||
siteConf = SiteConfiguration.get_solo()
|
||||
|
@ -92,6 +92,6 @@ class SpecialPublicationsView(PublicationListView):
|
|||
|
||||
|
||||
def latestPublication(req):
|
||||
""" Redirects to the latest standard publication """
|
||||
"""Redirects to the latest standard publication"""
|
||||
latestPubli = Publication.latest()
|
||||
return redirect(latestPubli.url)
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
from importlib.util import find_spec
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bocal.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError:
|
||||
# The above import may fail for some other reason. Ensure that the
|
||||
# issue is really that Django is missing to avoid masking other
|
||||
# exceptions on Python 2.
|
||||
try:
|
||||
import django
|
||||
except ImportError:
|
||||
if find_spec("django") is None:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
|
|
5
pyproject.toml
Normal file
5
pyproject.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
[tool.isort]
|
||||
profile = "black"
|
||||
|
||||
[tool.ruff.lint]
|
||||
ignore = ["F403", "F405"]
|
Loading…
Reference in a new issue