Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Xpirix/QGIS-Django into token_bas…
Browse files Browse the repository at this point in the history
…ed_authentication
  • Loading branch information
Xpirix committed Jan 9, 2024
2 parents d5e478d + a9dabb5 commit 634d727
Show file tree
Hide file tree
Showing 21 changed files with 5,969 additions and 39 deletions.
1 change: 1 addition & 0 deletions REQUIREMENTS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ pyjwt==1.7.1
djangorestframework-simplejwt==4.4
django-rest-auth==0.9.5
drf-yasg
django-matomo==0.1.6
1 change: 1 addition & 0 deletions dockerize/docker/REQUIREMENTS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ django-rest-multiple-models==2.1.3

django-preferences==1.0.0
PyWavefront==1.3.3
django-matomo==0.1.6
15 changes: 15 additions & 0 deletions qgis-app/plugins/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,25 @@ class Meta:
"tracker",
"repository",
"owners",
"maintainer",
"display_created_by",
"tags",
"server",
)

def __init__(self, *args, **kwargs):
super(PluginForm, self).__init__(*args, **kwargs)
self.fields['owners'].label = "Collaborators"

choices = (
(self.instance.created_by.pk, self.instance.created_by.username + " (Plugin creator)"),
)
for owner in self.instance.owners.exclude(pk=self.instance.created_by.pk):
choices += ((owner.pk, owner.username + " (Collaborator)"),)

self.fields['maintainer'].choices = choices
self.fields['maintainer'].label = "Maintainer"

def clean(self):
"""
Check author
Expand Down
29 changes: 29 additions & 0 deletions qgis-app/plugins/migrations/0005_plugin_maintainer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 2.2.25 on 2023-11-29 22:45

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

def populate_maintainer(apps, schema_editor):
Plugin = apps.get_model('plugins', 'Plugin')

# Set the maintainer as the plugin creator by default
for obj in Plugin.objects.all():
obj.maintainer = obj.created_by
obj.save()

class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('plugins', '0004_merge_20231122_0223'),
]

operations = [
migrations.AddField(
model_name='plugin',
name='maintainer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plugins_maintainer', to=settings.AUTH_USER_MODEL, verbose_name='Maintainer'),
),
migrations.RunPython(populate_maintainer),
]
18 changes: 18 additions & 0 deletions qgis-app/plugins/migrations/0006_plugin_display_created_by.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.25 on 2023-11-29 23:22

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('plugins', '0005_plugin_maintainer'),
]

operations = [
migrations.AddField(
model_name='plugin',
name='display_created_by',
field=models.BooleanField(default=False, verbose_name='Display "Created by" in plugin details'),
),
]
19 changes: 19 additions & 0 deletions qgis-app/plugins/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,22 @@ class Plugin(models.Model):
related_name="plugins_created_by",
on_delete=models.CASCADE,
)

# maintainer
maintainer = models.ForeignKey(
User,
verbose_name=_("Maintainer"),
related_name="plugins_maintainer",
on_delete=models.CASCADE,
blank=True,
null=True
)

display_created_by = models.BooleanField(
_('Display "Created by" in plugin details'),
default=False
)

author = models.CharField(
_("Author"),
help_text=_(
Expand Down Expand Up @@ -530,6 +546,7 @@ def save(self, keep_date=False, *args, **kwargs):
"""
Soft triggers:
* updates modified_on if keep_date is not set
* set maintainer to the plugin creator when not specified
"""
if self.pk and not keep_date:
import logging
Expand All @@ -538,6 +555,8 @@ def save(self, keep_date=False, *args, **kwargs):
self.modified_on = datetime.datetime.now()
if not self.pk:
self.modified_on = datetime.datetime.now()
if not self.maintainer:
self.maintainer = self.created_by
super(Plugin, self).save(*args, **kwargs)


Expand Down
9 changes: 8 additions & 1 deletion qgis-app/plugins/templates/plugins/plugin_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,16 @@ <h2>{{ object.name }}
<dt>{% trans "Author's email"%}</dt>
<dd> <a href="mailto:{{ object.email }}">{{ object.email }}</a></dd>
{% endif %}
{% if object.display_created_by %}
<dt>{% trans "Created by"%}</dt>
<dd>
<a href="{% url "user_details" object.created_by %}">{{ object.created_by }}</a>
</dd>

{% endif %}
<dt>{% trans "Maintainer"%}</dt>
<dd>
<a href="{% url "user_details" object.created_by %}">{{ object.created_by }}</a>
<a href="{% url "user_details" object.maintainer %}">{{ object.maintainer }}</a>
</dd>
{% if object.owners.count %}
<dt>{% trans "Collaborators"%}</dt>
Expand Down
2 changes: 1 addition & 1 deletion qgis-app/plugins/templates/plugins/plugin_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ <h2>{{ form_title }} {{ plugin }}</h2>
let element = document.getElementById('id_owners');
if(element) {
$('#id_owners').chosen({
placeholder_text_multiple: "Select Some Owners",
placeholder_text_multiple: "Select Some Collaborators",
no_results_text: "Oops, nothing found!"
});
clearInterval(checkElement);
Expand Down
5 changes: 3 additions & 2 deletions qgis-app/plugins/templates/plugins/plugin_list.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% extends 'plugins/plugin_base.html' %}{% load i18n bootstrap_pagination humanize static sort_anchor range_filter thumbnail %}
{% load local_timezone %}
{% block extrajs %}
<script type="text/javascript" src="{% static "js/jquery.cookie.js" %}"></script>
<script language="javascript">
Expand Down Expand Up @@ -93,8 +94,8 @@ <h2>{% if title %}{{title}}{% else %}{% trans "All plugins" %}{% endif %}</h2>
{% if object.author %}
<td><a title="{% trans "See all plugins by"%} {{ object.author }}" href="{% url "author_plugins" object.author %}">{{ object.author }}</a></td>
{% endif %}
<td>{{ object.latest_version_date|naturalday }}</td>
<td>{{ object.created_on|naturalday }}</td>
<td>{{ object.latest_version_date|local_timezone:"SHORT_NATURAL_DAY" }}</td>
<td>{{ object.created_on|local_timezone:"SHORT" }}</td>
<td><div><div class="star-ratings"><span style="width:{% widthratio object.average_vote 5 100 %}%" class="rating"></span></div> ({{ object.rating_votes }})</div></td>
<td>{% if object.stable %}<a href="{% url "version_download" object.package_name object.stable.version %}" title="{% trans "Download the stable version" %}" >{{ object.stable.version }}</a>{% else %}&mdash;{% endif %}</td>
<td>{% if object.experimental %}<a href="{% url "version_download" object.package_name object.experimental.version %}" title="{% trans "Download the experimental version" %}" >{{ object.experimental.version }}</a>{% else %}&mdash;{% endif %}</td>
Expand Down
3 changes: 2 additions & 1 deletion qgis-app/plugins/templates/plugins/version_detail.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% extends 'plugins/plugin_base.html' %}{% load i18n %}
{% load local_timezone %}
{% block content %}
<h2>{% trans "Version" %}: {{ version }}</h2>
<div class="pull-right"><a href="{% url "version_download" version.plugin.package_name version.version %}" class="btn btn-primary"><i class="icon-download-alt icon-white"></i> {% trans "Download" %}</a></div>
Expand All @@ -21,7 +22,7 @@ <h2>{% trans "Version" %}: {{ version }}</h2>
{% if version.changelog %}<dt>{% trans "Changelog" %}</dt><dd><pre>{{ version.changelog|wordwrap:80 }}</pre></dd>{% endif %}
<dt>{% trans "Approved" %}</dt><dd>{{ version.approved|yesno }}</dd>
<dt>{% trans "Author" %}</dt><dd>{{ version.created_by }}</dd>
<dt>{% trans "Uploaded" %}</dt><dd>{{ version.created_on }}</dd>
<dt>{% trans "Uploaded" %}</dt><dd>{{ version.created_on|local_timezone }}</dd>
<dt>{% trans "Minimum QGIS version" %}</dt><dd>{{ version.min_qg_version }}</dd>
<dt>{% trans "Maximum QGIS version" %}</dt><dd>{{ version.max_qg_version }}</dd>
<dt>{% trans "External dependencies (PIP install string)" %}</dt><dd>{{ version.external_deps }}</dd>
Expand Down
9 changes: 7 additions & 2 deletions qgis-app/plugins/templatetags/local_timezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@


@register.filter(name="local_timezone", is_safe=True)
def local_timezone(date):
def local_timezone(date, args="LONG"):
try:
utcdate = date.astimezone(pytz.utc).isoformat()
result = '<span class="user-timezone">%s</span>' % (utcdate,)
if args and str(args) == "SHORT":
result = '<span class="user-timezone-short">%s</span>' % (utcdate,)
elif args and str(args) == "SHORT_NATURAL_DAY":
result = '<span class="user-timezone-short-naturalday">%s</span>' % (utcdate,)
else:
result = '<span class="user-timezone">%s</span>' % (utcdate,)
except AttributeError:
result = date
return mark_safe(result)
101 changes: 101 additions & 0 deletions qgis-app/plugins/tests/test_change_maintainer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import os
from unittest.mock import patch

from django.urls import reverse
from django.test import Client, TestCase, override_settings
from django.contrib.auth.models import User
from django.core.files.uploadedfile import SimpleUploadedFile
from plugins.models import Plugin, PluginVersion
from plugins.forms import PluginForm

def do_nothing(*args, **kwargs):
pass

TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles"))

class PluginRenameTestCase(TestCase):
fixtures = [
"fixtures/styles.json",
"fixtures/auth.json",
"fixtures/simplemenu.json",
]

@override_settings(MEDIA_ROOT="api/tests")
def setUp(self):
self.client = Client()
self.url_upload = reverse('plugin_upload')

# Create a test user
self.user = User.objects.create_user(
username='testuser',
password='testpassword',
email='[email protected]'
)

# Log in the test user
self.client.login(username='testuser', password='testpassword')

# Upload a plugin for renaming test.
# This process is already tested in test_plugin_upload
valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_")
with open(valid_plugin, "rb") as file:
uploaded_file = SimpleUploadedFile(
"valid_plugin.zip_", file.read(),
content_type="application/zip")

self.client.post(self.url_upload, {
'package': uploaded_file,
})

self.plugin = Plugin.objects.get(name='Test Plugin')
self.plugin.save()

@patch("plugins.tasks.generate_plugins_xml.delay", new=do_nothing)
@patch("plugins.validator._check_url_link", new=do_nothing)
def test_change_maintainer(self):
"""
Test change maintainer for plugin update
"""
package_name = self.plugin.package_name
self.url_plugin_update = reverse('plugin_update', args=[package_name])
self.url_add_version = reverse('version_create', args=[package_name])

# Test GET request
response = self.client.get(self.url_plugin_update)
self.assertEqual(response.status_code, 200)
self.assertIsInstance(response.context['form'], PluginForm)
self.assertEqual(response.context['form']['maintainer'].value(), self.user.pk)


# Test POST request to change maintainer

response = self.client.post(self.url_plugin_update, {
'description': self.plugin.description,
'about': self.plugin.about,
'author': self.plugin.author,
'email': self.plugin.email,
'tracker': self.plugin.tracker,
'repository': self.plugin.repository,
'maintainer': 1,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(Plugin.objects.get(name='Test Plugin').maintainer.pk, 1)

# Test POST request with new version

valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_")
with open(valid_plugin, "rb") as file:
uploaded_file = SimpleUploadedFile(
"valid_plugin_0.0.2.zip_", file.read(),
content_type="application/zip_")

response = self.client.post(self.url_add_version, {
'package': uploaded_file,
'experimental': False,
'changelog': ''
})
self.assertEqual(response.status_code, 302)
self.assertEqual(Plugin.objects.get(name='Test Plugin').maintainer.pk, 1)

def tearDown(self):
self.client.logout()
2 changes: 1 addition & 1 deletion qgis-app/plugins/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def _check_required_metadata(metadata):
if md not in dict(metadata) or not dict(metadata)[md]:
raise ValidationError(
_(
'Cannot find metadata <strong>%s</strong> in metadata source <code>%s</code>.<br />For further informations about metadata, please see: <a target="_blank" href="http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/plugins.html#plugin-metadata-table">metadata documentation</a>'
'Cannot find metadata <strong>%s</strong> in metadata source <code>%s</code>.<br />For further informations about metadata, please see: <a target="_blank" href="https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/plugins/plugins.html#metadata-txt">metadata documentation</a>'
)
% (md, dict(metadata).get("metadata_source"))
)
Expand Down
7 changes: 5 additions & 2 deletions qgis-app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"preferences",
# styles:
"styles",
"matomo"
]

TEMPLATES = [
Expand Down Expand Up @@ -334,9 +335,11 @@
CELERY_BROKER_URL = BROKER_URL
CELERY_RESULT_BACKEND = CELERY_BROKER_URL


# Token access and refresh validity
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=15),
'REFRESH_TOKEN_LIFETIME': timedelta(days=15),
}
}

MATOMO_SITE_ID="1"
MATOMO_URL="//matomo.qgis.org/"
6 changes: 5 additions & 1 deletion qgis-app/settings_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
# models (sharing .model3 file feature)
"models",
"wavefronts",
"matomo"
]

DATABASES = {
Expand Down Expand Up @@ -129,4 +130,7 @@
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=365*1000),
'REFRESH_TOKEN_LIFETIME': timedelta(days=365*1000)
}
}

MATOMO_SITE_ID="1"
MATOMO_URL="//matomo.qgis.org/"
40 changes: 40 additions & 0 deletions qgis-app/static/js/local_timezone-1.0.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Replace the date with local timezone

$(".user-timezone").each(function (i) {
let localDate = toUserTimeZone($(this).text());
$(this).text(localDate);
})

$(".user-timezone-short").each(function (i) {
let localDate = toUserTimeZone($(this).text(), withTime=false);
$(this).text(localDate);
})

$(".user-timezone-short-naturalday").each(function (i) {
let localDate = toUserTimeZone($(this).text(), withTime=false, isNaturalDay=true);
$(this).text(localDate);
})

function toUserTimeZone(date, withTime=true, isNaturalDay=false) {
try {
date = new Date(date);
let options = {
year: 'numeric', month: 'short', day: 'numeric'
}
if (withTime) {
options['hour'] = '2-digit'
options['minute'] = '2-digit'
options['timeZoneName'] = 'short'
}
const diffInDays = moment().diff(moment(date), 'days');

if (diffInDays <= 1 && isNaturalDay) {
const distance = moment(date).fromNow();
return distance
}
return date.toLocaleDateString([], options);
} catch (e) {
return date;
}
}

Loading

0 comments on commit 634d727

Please sign in to comment.