Skip to content

Commit e83735c

Browse files
authored
Merge pull request #242 from Xpirix/allow_empty_plugin
Allow uploading an empty plugin
2 parents cb48102 + c6c6c72 commit e83735c

File tree

9 files changed

+615
-24
lines changed

9 files changed

+615
-24
lines changed

qgis-app/plugins/forms.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,67 @@ def clean(self):
194194
return super(PluginVersionForm, self).clean()
195195

196196

197+
class PluginCreateForm(forms.Form):
198+
"""
199+
Form for creating an empty plugin (no versions yet).
200+
Minimal fields - the rest will be populated from metadata.txt on first upload.
201+
"""
202+
203+
required_css_class = "required"
204+
name = forms.CharField(
205+
label=_("Plugin name"),
206+
help_text=_("A display name for your plugin. It must be unique."),
207+
max_length=256,
208+
)
209+
package_name = forms.CharField(
210+
label=_("Package name"),
211+
help_text=_(
212+
"This must be the main plugin folder name inside your zip file. Use only ASCII letters, digits, '-' or '_'. This cannot be changed later."
213+
),
214+
max_length=256,
215+
)
216+
217+
def clean_package_name(self):
218+
package_name = self.cleaned_data.get("package_name", "").strip()
219+
if not re.match(r"^[A-Za-z][A-Za-z0-9-_]+$", package_name):
220+
raise ValidationError(
221+
_(
222+
"Package name must start with a letter and can contain only ASCII letters, digits, '-' or '_'."
223+
)
224+
)
225+
existing = Plugin.objects.filter(package_name__iexact=package_name).first()
226+
if existing:
227+
raise ValidationError(
228+
_("A plugin with a similar package name (%s) already exists.")
229+
% existing.package_name
230+
)
231+
return package_name
232+
233+
def clean_name(self):
234+
name = self.cleaned_data.get("name", "").strip()
235+
existing = Plugin.objects.filter(name__iexact=name).first()
236+
if existing:
237+
raise ValidationError(
238+
_("A plugin with a similar name (%s) already exists.") % existing.name
239+
)
240+
return name
241+
242+
def save(self, created_by, commit=True):
243+
plugin = Plugin()
244+
# Set all required fields first
245+
plugin.created_by = created_by
246+
plugin.author = created_by.get_full_name() or created_by.username
247+
plugin.email = created_by.email or "noreply@example.com"
248+
plugin.description = "Placeholder - will be updated on first version upload"
249+
plugin.package_name = self.cleaned_data.get("package_name")
250+
plugin.name = self.cleaned_data.get("name")
251+
252+
if commit:
253+
# Skip model validation since we already validated in the form
254+
plugin.save()
255+
return plugin
256+
257+
197258
class PackageUploadForm(forms.Form):
198259
"""
199260
Single step upload for new plugins
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{% extends 'plugins/plugin_base.html' %}{% load i18n %}
2+
{% block content %}
3+
<h2>{% trans "Create an empty plugin" %}</h2>
4+
<div class="container rich">
5+
<div class="cont coloring-6">
6+
<p>{% trans "Create a plugin entry without uploading a package. This is useful if you want to generate a token and upload versions later via the API." %}</p>
7+
<p>{% trans "Only the package name and plugin name are required here. All other plugin details (description, author, homepage, etc.) will be automatically populated from the metadata.txt file when you upload your first version." %}</p>
8+
</div>
9+
</div>
10+
<div class="container rich mt-3">
11+
<div class="cont coloring-2">
12+
<p class="has-text-weight-bold">
13+
<i class="fas fa-exclamation-triangle"></i>
14+
{% trans "Important: The package name you specify below must exactly match the main folder name inside your plugin's zip file when you upload versions later." %}
15+
</p>
16+
</div>
17+
</div>
18+
{% if form.errors %}
19+
<div class="notification is-danger is-light">
20+
<button class="delete" data-dismiss="alert">&times;</button>
21+
{% trans "The form contains errors and cannot be submitted, please check the fields highlighted in red." %}
22+
</div>
23+
{% endif %}
24+
{% if form.non_field_errors %}
25+
<div class="notification is-danger is-light">
26+
<button class="delete" data-dismiss="alert">&times;</button>
27+
{% for error in form.non_field_errors %}
28+
{{ error }}<br />
29+
{% endfor %}
30+
</div>
31+
{% endif %}
32+
33+
<form action="" method="post" class="horizontal" enctype="multipart/form-data">{% csrf_token %}
34+
{% include "plugins/form_snippet.html" %}
35+
<div class="mt-3 has-text-centered">
36+
<button class="button is-success is-medium" type="submit">
37+
<span class="icon is-small">
38+
<i class="fas fa-plus"></i>
39+
</span>
40+
<span>{% trans "Create plugin" %}</span>
41+
</button>
42+
</div>
43+
</form>
44+
{% endblock %}

qgis-app/plugins/templates/plugins/plugin_sidebar.html

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
{% load i18n plugins_tagcloud simplemenu_tags static plugin_utils %}
22
<nav id="sidebar" class="sidebar">
33
<ul class="content-wrapper">
4-
<li>
5-
<a class="button is-success is-medium" href="{% url "plugin_upload" %}">
6-
<i class="fas fa-upload"></i>
7-
{% trans "Upload a plugin" %}
4+
<div class="buttons mb-0">
5+
<a class="button is-success is-fullwidth" href="{% url "plugin_upload" %}">
6+
<span class="icon">
7+
<i class="fas fa-upload"></i>
8+
</span>
9+
<span>{% trans "Upload a plugin" %}</span>
810
</a>
9-
</li>
11+
<a class="button is-success is-outlined is-fullwidth" href="{% url "plugin_create_empty" %}">
12+
<span class="icon">
13+
<i class="fas fa-plus"></i>
14+
</span>
15+
<span>{% trans "Create empty plugin" %}</span>
16+
</a>
17+
</div>
1018
<hr/>
1119

1220
{% get_navigation_menu user as menu %}

qgis-app/plugins/templates/plugins/plugin_token_list.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ <h2>{% trans "Tokens for" %} {{ plugin.name }}</h2>
66
<div style="margin:0; width:fit-content;">
77
<h2>
88
<button type="submit" name="plugin_token_create" id="plugin_token_create"
9-
value="{% trans "Generate a New Token" %}" class="button" style="padding: 10px">
10-
<i class="fas fa-plus" style="vertical-align: middle;"></i>
11-
&nbsp;{% trans "Generate a New Token" %}
9+
value="{% trans "Generate a New Token" %}" class="button is-light">
10+
<span class="icon">
11+
<i class="fas fa-plus"></i>
12+
</span>
13+
<span>{% trans "Generate a New Token" %}</span>
1214
</button>
1315
</h2>
1416
</div>
@@ -74,4 +76,4 @@ <h2>
7476
direction: rtl;
7577
}
7678
</style>
77-
{% endblock %}
79+
{% endblock %}

qgis-app/plugins/templates/plugins/plugin_upload.html

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
{% extends 'plugins/plugin_base.html' %}{% load i18n %}
22
{% block content %}
33
<h2>{% trans "Upload a plugin" %}</h2>
4-
4+
55
<div class="container rich">
66
<div class="cont coloring-6">
77
<p>{% trans "To upload a new plugin or update an existing one, you can specify the zipped file in this form." %}</p>
88
<p>{% trans "Alternatively, to update an existing plugin, you can also open the plugin's details view and add a new version from there." %}</p>
9+
<p>
10+
{% trans "Need to initialize a plugin without uploading a package?" %}
11+
<a href="{% url "plugin_create_empty" %}">{% trans "Create an empty plugin" %}</a>.
12+
</p>
913
</div>
1014
</div>
1115
{% if form.non_field_errors %}
@@ -20,7 +24,7 @@ <h2>{% trans "Upload a plugin" %}</h2>
2024
<form action="" method="post" enctype="multipart/form-data" class="horizontal">{% csrf_token %}
2125
{% include "plugins/form_snippet.html" %}
2226
<div class="mt-3 has-text-centered">
23-
<button class="button is-success is-large" type="submit">
27+
<button class="button is-success is-medium" type="submit">
2428
<span class="icon is-small">
2529
<i class="fas fa-upload"></i>
2630
</span>
@@ -35,10 +39,10 @@ <h2>{% trans "Upload a plugin" %}</h2>
3539
<div class="container rich">
3640
<div class="cont coloring-2">
3741
<p>
38-
{% blocktrans %}
39-
Please note that by uploading a plugin to the official QGIS plugin repository,
40-
you agree that we will use your email to contact you. We will only contact
41-
you for matters relating to the management of plugins and will not make
42+
{% blocktrans %}
43+
Please note that by uploading a plugin to the official QGIS plugin repository,
44+
you agree that we will use your email to contact you. We will only contact
45+
you for matters relating to the management of plugins and will not make
4246
your email available to third parties for marketing purposes.
4347
{% endblocktrans %}
4448

@@ -48,16 +52,16 @@ <h2>{% trans "Upload a plugin" %}</h2>
4852
<div class="container rich">
4953
<div class="cont coloring-2">
5054
<p>
51-
{% blocktrans %}
52-
By uploading your plugin to the QGIS plugin repository,
53-
you agree to keep your email address current and to be
54-
responsive to any correspondence we may send you
55-
regarding the management of your plugin. We require
56-
this in order to be able to provide our users assurance
57-
that the plugins we host are well maintained and will be
55+
{% blocktrans %}
56+
By uploading your plugin to the QGIS plugin repository,
57+
you agree to keep your email address current and to be
58+
responsive to any correspondence we may send you
59+
regarding the management of your plugin. We require
60+
this in order to be able to provide our users assurance
61+
that the plugins we host are well maintained and will be
5862
fixed should serious issues arise during their use.
59-
We reserve the right to hide or remove plugins in cases
60-
where the plugin author is not contactable and there
63+
We reserve the right to hide or remove plugins in cases
64+
where the plugin author is not contactable and there
6165
are issues reported about a plugin.
6266
{% endblocktrans %}
6367
</p>

0 commit comments

Comments
 (0)