Commit 59d2c7df authored by Elger Jonker's avatar Elger Jonker

[wip] location markers, map country config, google maps, easy add organizations


Former-commit-id: 0a321016
parent 53ed60a4
import logging
from django.contrib import admin
from django.db import transaction
from django.utils import timezone
from import_export.admin import ImportExportModelAdmin
from jet.admin import CompactInline
from failmap.game.models import Contest, OrganizationSubmission, Team, UrlSubmission
from failmap.organizations.models import Organization, OrganizationType, Url
from failmap.organizations.models import Coordinate, Organization, OrganizationType, Url
log = logging.getLogger(__package__)
......@@ -77,6 +79,7 @@ class UrlSubmissionAdmin(ImportExportModelAdmin, admin.ModelAdmin):
actions = []
@transaction.atomic
def accept(self, request, queryset):
for urlsubmission in queryset:
......@@ -85,6 +88,7 @@ class UrlSubmissionAdmin(ImportExportModelAdmin, admin.ModelAdmin):
if urlsubmission.has_been_accepted or urlsubmission.has_been_rejected:
continue
# it's possible that the url already is in the system. If so, tie that to the submitted organization.
try:
url = Url.objects.all().get(url=urlsubmission.url)
except Url.DoesNotExist:
......@@ -92,13 +96,14 @@ class UrlSubmissionAdmin(ImportExportModelAdmin, admin.ModelAdmin):
url = Url(url=urlsubmission.url)
url.save()
# organization might also be added... that not really a problem.
# the organization is already inside the submission and should exist in most cases.
try:
url.organization.add(urlsubmission.for_organization)
url.save()
except Exception as e:
log.error(e)
# add some tracking data to the submission
urlsubmission.url_in_system = url
urlsubmission.has_been_accepted = True
urlsubmission.save()
......@@ -107,6 +112,7 @@ class UrlSubmissionAdmin(ImportExportModelAdmin, admin.ModelAdmin):
accept.short_description = "✅ Accept"
actions.append('accept')
@transaction.atomic
def reject(self, request, queryset):
for urlsubmission in queryset:
urlsubmission.has_been_rejected = True
......@@ -126,11 +132,13 @@ class OrganizationSubmissionAdmin(ImportExportModelAdmin, admin.ModelAdmin):
'added_by_team__participating_in_contest__name')
fields = ('added_by_team', 'organization_country', 'organization_type_name', 'organization_name',
'organization_address', 'organization_address_geocoded', 'organization_in_system', 'has_been_accepted',
'has_been_rejected', 'added_on')
'organization_address', 'organization_address_geocoded', 'organization_wikipedia',
'organization_wikidata_code', 'has_been_accepted',
'has_been_rejected', 'organization_in_system', 'added_on',)
actions = []
@transaction.atomic
def accept(self, request, queryset):
for osm in queryset:
......@@ -140,13 +148,14 @@ class OrganizationSubmissionAdmin(ImportExportModelAdmin, admin.ModelAdmin):
continue
try:
# todo: set country to config of competition, if any
# this might revive some old organizations, so domain knowledge is required.
# In this case the organization already exists with the same name, type and alive.
# this means we don't need to add a new one, or with new coordinates.
Organization.objects.all().get(
name=osm.organization_name,
country='NL',
country=osm.organization_country,
is_dead=False,
type=OrganizationType.objects.get(name=osm.organization_type_name)).exists()
type=OrganizationType.objects.get(name=osm.organization_type_name))
except Organization.DoesNotExist:
# Create a new one
# address and evidence are saved elsewhere. Since we have a reference we can auto-update after
......@@ -154,13 +163,25 @@ class OrganizationSubmissionAdmin(ImportExportModelAdmin, admin.ModelAdmin):
# adding this data in the system again(?)
new_org = Organization(
name=osm.organization_name,
country='NL',
country=osm.organization_country,
is_dead=False,
type=OrganizationType.objects.get(name=osm.organization_type_name),
created_on=timezone.now(),
)
new_org.save()
# of course it has a new coordinate
new_coordinate = Coordinate(
organization=new_org,
geojsontype="Point",
area=osm.organization_address_geocoded,
edit_area=osm.organization_address_geocoded,
created_on=timezone.now(),
creation_metadata="Accepted organization submission"
)
new_coordinate.save()
# and save tracking information
osm.organization_in_system = new_org
osm.has_been_accepted = True
osm.save()
......@@ -169,6 +190,7 @@ class OrganizationSubmissionAdmin(ImportExportModelAdmin, admin.ModelAdmin):
accept.short_description = "✅ Accept"
actions.append('accept')
@transaction.atomic
def reject(self, request, queryset):
for organizationsubmission in queryset:
organizationsubmission.has_been_rejected = True
......
......@@ -3,7 +3,6 @@ import time
import tldextract
from dal import autocomplete
# from django.contrib.gis import forms # needs gdal, which...
from django import forms
from django.db import transaction
from django.db.models.functions import Lower
......@@ -17,9 +16,6 @@ from failmap.game.models import Contest, OrganizationSubmission, Team, UrlSubmis
from failmap.organizations.models import Organization, OrganizationType, Url
from failmap.scanners.scanner.http import resolves
# todo: callback on edit address, put result in leaflet:
log = logging.getLogger(__package__)
......@@ -46,10 +42,6 @@ class ContestForm(forms.Form):
# code='invalid', )
# todo: this doesn't work yet
# don't show the secret (only in the source)
# should this be in forms.py or in admin.py?
# https://stackoverflow.com/questions/17523263/how-to-create-password-field-in-model-django
class TeamForm(forms.Form):
contest = Contest()
......@@ -72,8 +64,8 @@ class TeamForm(forms.Form):
team = cleaned_data.get("team")
secret = cleaned_data.get("secret")
# validate secret, add some timing...
time.sleep(1) # wait a second to deter brute force attacks (you can still do them)
# wait a second to deter brute force attacks (you can still do them)
time.sleep(1)
# it's possible NOT to select a team, in that case, don't try and validate secret.
if team:
......@@ -85,14 +77,7 @@ class TeamForm(forms.Form):
code='invalid',
)
# class Meta:
# model = UrlSubmission # not bound to a model, we have to write save ourselves since we want to do
# a bit of dirty hacks (to prevent more N-N fields).
# fields = ('team', 'secret', )
# http://django-autocomplete-light.readthedocs.io/en/master/tutorial.html
class OrganisationSubmissionForm(forms.Form):
contest = None
......@@ -110,13 +95,13 @@ class OrganisationSubmissionForm(forms.Form):
)
latitude = forms.DecimalField(
max_digits=9,
decimal_places=6,
max_digits=21,
decimal_places=17,
)
longitude = forms.DecimalField(
max_digits=9,
decimal_places=6,
max_digits=21,
decimal_places=17,
)
# "The full address of this organization, at it's current address. If there are multiple addresses, "
......@@ -157,9 +142,7 @@ class OrganisationSubmissionForm(forms.Form):
help_text=""
)
# todo: clean the geolocated address to fit the rest of the system. The ugly
# POINT -() etc has to be formatted according our normal layout so it can be processed in the admin
# interface.
# todo: clean the geolocated address to fit the rest of the system.
def clean(self):
# verify that an organization of this type is not in the database yet...
......@@ -173,7 +156,7 @@ class OrganisationSubmissionForm(forms.Form):
if exists:
raise ValidationError(
_('This organization %(organization)s already exists in the database for this group.'),
_('This organization %(organization)s already exists in the database for this type / layer.'),
code='invalid',
params={'organization': name},
)
......@@ -188,20 +171,15 @@ class OrganisationSubmissionForm(forms.Form):
@transaction.atomic
def save(self, team):
organization_country = self.cleaned_data.get('organization_country', None)
organization_type_name = self.cleaned_data.get('organization_type_name', None)
organization_country = self.contest.target_country
organization_name = self.cleaned_data.get('organization_name', None)
lat = self.cleaned_data.get('latitude', None)
lng = self.cleaned_data.get('longitude', None)
organization_address = self.cleaned_data.get('organization_address', None)
organization_evidence = self.cleaned_data.get('organization_address', None)
organization_wikipedia = self.cleaned_data.get('organization_wikipedia', None)
organization_wikidata = self.cleaned_data.get('organization_wikidata', None)
# organization_address_geocoded comes from the new input... todo
organization_address_geocoded = self.cleaned_data.get('organization_address_geocoded', None)
if not all([organization_name, organization_type_name, organization_address, organization_evidence]):
raise forms.ValidationError(
"Missing some fields..."
)
organization_type_name = self.cleaned_data.get('organization_type_name', None)
organization_evidence = self.cleaned_data.get('organization_evidence', None)
submission = OrganizationSubmission(
added_by_team=Team.objects.get(pk=team),
......@@ -211,7 +189,7 @@ class OrganisationSubmissionForm(forms.Form):
organization_type_name=organization_type_name,
organization_wikipedia=organization_wikipedia,
organization_wikidata_code=organization_wikidata,
organization_address_geocoded=organization_address_geocoded,
organization_address_geocoded=[lng, lat],
organization_country=organization_country,
added_on=timezone.now(),
has_been_accepted=False,
......@@ -430,7 +408,9 @@ class UrlSubmissionForm(forms.Form):
# This URL %(url)s is already suggested for organization %(organization)s
continue
new.append(website)
# only add it once to the new list :)
if website not in new:
new.append(website)
self.cleaned_data['websites'] = new
......
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map33 {
height: 400px;
}
#map33 {
height: 500px;
}
#description {
font-family: Roboto;
font-size: 15px;
font-weight: 300;
}
#description {
font-family: Roboto, sans-serif;
font-size: 15px;
font-weight: 300;
}
#infowindow-content .title {
font-weight: bold;
}
#infowindow-content .title {
font-weight: bold;
}
#infowindow-content {
display: none;
}
#infowindow-content {
display: none;
}
#map #infowindow-content {
display: inline;
}
#map #infowindow-content {
display: inline;
}
.pac-card {
margin: 10px 10px 0 0;
border-radius: 2px 0 0 2px;
box-sizing: border-box;
-moz-box-sizing: border-box;
outline: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
background-color: #fff;
font-family: Roboto;
}
.pac-card {
margin: 10px 10px 0 0;
border-radius: 2px 0 0 2px;
box-sizing: border-box;
-moz-box-sizing: border-box;
outline: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
background-color: #fff;
font-family: Roboto, sans-serif;
width: 600px;
}
#pac-container {
padding-bottom: 12px;
margin-right: 12px;
}
#pac-container {
padding-bottom: 12px;
margin-right: 12px;
}
.pac-controls {
display: inline-block;
padding: 5px 11px;
}
.pac-controls {
display: inline-block;
padding: 5px 11px;
}
.pac-controls label {
font-family: Roboto;
font-size: 13px;
font-weight: 300;
}
.pac-controls label {
font-family: Roboto, sans-serif;
font-size: 13px;
font-weight: 300;
}
#pac-input {
background-color: #fff;
font-family: Roboto;
font-size: 15px;
font-weight: 300;
margin-left: 12px;
padding: 0 11px 0 13px;
text-overflow: ellipsis;
width: 400px;
}
#pac-input {
background-color: #fff;
font-family: Roboto, sans-serif;
font-size: 15px;
font-weight: 300;
margin-left: 12px;
padding: 0 11px 0 13px;
text-overflow: ellipsis;
width: 570px;
}
#pac-input:focus {
border-color: #4d90fe;
}
#pac-input:focus {
border-color: #4d90fe;
}
#title {
color: #fff;
background-color: #4d90fe;
font-size: 25px;
font-weight: 500;
padding: 6px 12px;
}
\ No newline at end of file
#title {
color: #fff;
background-color: #4d90fe;
font-size: 25px;
font-weight: 500;
padding: 6px 12px;
}
\ No newline at end of file
......@@ -5,8 +5,8 @@
<head>
<meta charset="UTF-8">
<title>Failmap - THE GAME</title>
{% leaflet_js plugins="forms" %}
{% leaflet_css plugins="forms" %}
{% leaflet_js plugins="forms" %}
{% leaflet_css plugins="forms" %}
<script type="text/javascript" src="{% static 'js/vendor/jquery-3.2.1.js' %}"></script>
<link rel="stylesheet" type="text/css" href="{% static 'css/vendor/leaflet.css' %}"/>
......@@ -18,55 +18,53 @@
<style>
body {
min-height: 2000px;
padding-top: 70px;
padding-top: 70px;
}
.dropdown:hover .dropdown-menu {
display: block;
display: block;
}
html,
body {
height: 100%;
/* The html and body elements cannot have any padding or margin. */
}
/* Wrapper for page content to push down footer */
#wrap {
min-height: 100%;
height: auto !important;
height: 100%;
/* Negative indent footer by it's height */
margin: 0 auto -30px;
}
/* Sticky footer styles
-------------------------------------------------- */
html,
body {
height: 100%;
/* The html and body elements cannot have any padding or margin. */
}
/* Wrapper for page content to push down footer */
#wrap {
min-height: 100%;
height: auto !important;
height: 100%;
/* Negative indent footer by it's height */
margin: 0 auto -30px;
}
/* Set the fixed height of the footer here */
#push,
#footer {
height: 30px;
margin-top: 3px;
font-size: 0.9em;
font-style: italic;
border-top: 1px solid black;
}
#footer {
background-color: #f5f5f5;
}
/* Lastly, apply responsive CSS fixes as necessary */
@media (max-width: 767px) {
/* Set the fixed height of the footer here */
#push,
#footer {
margin-left: -20px;
margin-right: -20px;
padding-left: 20px;
padding-right: 20px;
height: 30px;
margin-top: 58px;
font-size: 0.9em;
font-style: italic;
border-top: 1px solid #b9b6b6;
padding: 5px;
color: #8e8c8c
}
}
#footer {
background-color: #f7f3ee;
}
/* Lastly, apply responsive CSS fixes as necessary */
@media (max-width: 767px) {
#footer {
margin-left: -20px;
margin-right: -20px;
padding-left: 20px;
padding-right: 20px;
}
}
</style>
</head>
......@@ -74,20 +72,32 @@
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-collapse">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target=".navbar-collapse">
<span class="sr-only">{% trans "Toggle navigation" %}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"><i class="fas fa-map-marker-alt"></i>{% trans "Game" %}</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-left">
<li><a href="/game/scores/">Scores</a></li>
<li><a href="/game/contests/">Contest</a></li>
<li><a href="/game/team/">Teams</a></li>
<li class="dropdown">
<a href="/game/submit_organization/" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Submit Organization <span class="caret"></span></a>
<a href="/game/submit_organization/" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">Submit Organization <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/game/submit_organization/">Submit Organization</a></li>
<li><a href="/game/submitted_organizations/">Submitted Organizations</a></li>
</ul>
</li>
<li class="dropdown">
<a href="/game/submit_url/" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Submit Url<span class="caret"></span></a>
<a href="/game/submit_url/" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">Submit Url<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/game/submit_url/">Submit Url</a></li>
<li><a href="/game/submitted_urls/">Submitted Urls</a></li>
......@@ -97,7 +107,8 @@
<ul class="nav navbar-nav navbar-right">
<li><a href="/game/rules_help/">? Rules / Help</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Log on/off<span class="caret"></span></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">Log on/off<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/authentication/logout/">Logout</a></li>
<li><a href="/authentication/login/">Login</a></li>
......@@ -111,17 +122,19 @@
<div id="wrap">
<div class="container">
{% block content %}
{% endblock %}
{% block content %}
{% endblock %}
</div>
</div>
<div id="footer">
<div class="container">
All entered data can be downloaded from the Failmap Website of your country. For a list, see <a href="https://failmap.org">failmap.org</a>.
You can then process it further yourself or import it in another open source <a href="https://gitlab.com/failmap/">failmap instance</a>.
</div>
<div class="container">
All entered data can be downloaded from the Failmap Website of your country. For a list, see <a
href="https://failmap.org">failmap.org</a>.
You can then process it further yourself or import it in another open source <a
href="https://gitlab.com/failmap/">failmap instance</a>.
</div>
</div>
</body>
</html>
......@@ -19,9 +19,6 @@
</div>
{% endif %}
{% comment "Find out how to re-use a list." %}
{% endcomment %}
<h2>Currently open</h2>
<table class="table">
<thead>
......
......@@ -2,8 +2,9 @@ import logging
from datetime import datetime
import pytz
from babel import languages
from constance import config
from dal import autocomplete
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count, Q
......@@ -85,23 +86,31 @@ def submit_organisation(request):
if not request.session.get('team'):
return redirect('/game/team/')
contest = get_default_contest(request)
language = languages.get_official_languages(contest.target_country)[0]
if request.POST:
form = OrganisationSubmissionForm(request.POST, team=request.session.get('team'),
contest=get_default_contest(request))
form = OrganisationSubmissionForm(request.POST, team=request.session.get('team'), contest=contest)
if form.is_valid():
# manually saving the form, this is not your normal 1 to 1 save.
form.save(team=request.session.get('team'))
form = OrganisationSubmissionForm(team=request.session.get('team'), contest=get_default_contest(request))
return render(request, 'game/submit_organisation.html', {'form': form, 'success': True})
form = OrganisationSubmissionForm(team=request.session.get('team'), contest=contest)
return render(request, 'game/submit_organisation.html',
{'form': form,
'success': True,
'GOOGLE_MAPS_API_KEY': config.GOOGLE_MAPS_API_KEY,
'target_country': contest.target_country,
'language': language
})
else:
form = OrganisationSubmissionForm(team=request.session.get('team'), contest=get_default_contest(request),)
form = OrganisationSubmissionForm(team=request.session.get('team'), contest=contest,)
return render(request, 'game/submit_organisation.html', {'form': form,
'GOOGLE_MAPS_API_KEY': settings.GOOGLE_MAPS_API_KEY})
'GOOGLE_MAPS_API_KEY': config.GOOGLE_MAPS_API_KEY,
'target_country': contest.target_country,
'language': language})
def scores(request):
......
This diff is collapsed.
// Registry Sentry for error reporting
let sentry_token = document.head.querySelector("[name=sentry_token]").getAttribute('content');
let version = document.head.querySelector("[name=version]").getAttribute('content');
let country = document.head.querySelector("[name=country]").getAttribute('content');
if (sentry_token) {
Raven.config(sentry_token, {release: version}).install();
}
......@@ -91,7 +92,7 @@ var dynamic_translations = function(){
};
$(document).ready(function () {
failmap.initialize("nl");
failmap.initialize(country);
views(); // start all vues
lazyload(); // allow for lazy loading of images
......
......@@ -10,6 +10,7 @@
<meta name="issue_mailto" content="{{ config.PROJECT_ISSUE_MAIL }}">
<meta name="sentry_token" content="{{ sentry_token }}">
<meta name="version" content="{{ version }}">
<meta name="country" content="{{ country }}">
<title>{% trans "Site Title" %}</title>
......
......@@ -5,6 +5,7 @@ from datetime import datetime, timedelta
import pytz
import simplejson as json
from cacheops import cached
from constance import config
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib.humanize.templatetags.humanize import naturaltime
......@@ -66,11 +67,11 @@ def get_country(code: str):
match = re.search(r"[A-Z]{2}", code)
if not match:
# https://what-if.xkcd.com/53/
return "NL"
return config.PROJECT_COUNTRY
# check if we have a country like that in the db:
if not Organization.objects.all().filter(country=code).exists():
return "NL"
return config.PROJECT_COUNTRY
return code
......@@ -82,7 +83,7 @@ def get_default_country(request, ):
).order_by('display_order').values_list('country', flat=True).first()
if not country:
return 'NL'
return config.PROJECT_COUNTRY
return JsonResponse([country], safe=False, encoder=JSEncoder)
......@@ -237,6 +238,7 @@ def index(request):
'version': __version__,
'admin': settings.ADMIN,
'sentry_token': settings.SENTRY_TOKEN,
'country': config.PROJECT_COUNTRY
})
......
# Generated by Django 2.0.7 on 2018-08-14 14:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scanners', '0041_tlsscan'),
]
operations = [
migrations.AlterField(
model_name='endpointgenericscan',
name='domain',