From 156b91eb5935df4afdb8f0f0311d36537808c2f5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 9 Aug 2011 23:16:00 -0500 Subject: Use new package details link tag in templates This replaces a lot of boilerplate we had everywhere, and makes sure things like the title are consistent across all links. Signed-off-by: Dan McGee --- templates/packages/details.html | 17 +++++------------ templates/packages/flag.html | 4 +++- templates/packages/flag_confirmed.html | 8 ++++---- templates/packages/flagged.html | 7 +++---- templates/packages/packages_list.html | 5 +++-- templates/packages/search.html | 3 +-- templates/packages/signoffs.html | 5 +++-- 7 files changed, 22 insertions(+), 27 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/details.html b/templates/packages/details.html index 7972b9ab..8e3c0022 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load cache %} +{% load package_extras %} {% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.full_version }} - Package Details{% endblock %} {% block navbarclass %}anb-packages{% endblock %} @@ -82,20 +83,14 @@

Versions Elsewhere

{% with pkg.split_packages as splits %}{% if splits %} Split Packages: - - {% for s in splits %} - {{ s.pkgname }}
- {% endfor %} - + {% for s in splits %}{% pkg_details_link s %}
{% endfor %} {% endif %}{% endwith %} {% else %} Base Package: {% if pkg.base_package %} - {{ pkg.pkgbase }} + {% pkg_details_link pkg.base_package %} {% else %} {{ pkg.pkgbase }} @@ -165,8 +160,7 @@

{% ifequal depend.pkg None %}
  • {{ depend.dep.depname }} (virtual)
  • {% else %} -
  • {{ depend.dep.depname }}{{ depend.dep.depvcmp|default:"" }} +
  • {% pkg_details_link depend.pkg %}{{ depend.dep.depvcmp|default:"" }} {% if depend.pkg.repo.testing %}(testing){% endif %} {% if depend.dep.optional %}(optional){% endif %} {% if depend.dep.description %}- {{ depend.dep.description }}{% endif %} @@ -188,8 +182,7 @@

    {% if rqdby %}
      {% for req in rqdby %} -
    • {{ req.pkg.pkgname }} +
    • {% pkg_details_link req.pkg %} {% if req.pkg.repo.testing %}(testing){% endif %} {% if req.optional %}(optional){% endif %}
    • diff --git a/templates/packages/flag.html b/templates/packages/flag.html index 4a3c6966..261d6066 100644 --- a/templates/packages/flag.html +++ b/templates/packages/flag.html @@ -1,4 +1,6 @@ {% extends "base.html" %} +{% load package_extras %} + {% block title %}Arch Linux - Flag Package - {{ package.pkgname }}{% endblock %} {% block navbarclass %}anb-packages{% endblock %} @@ -13,7 +15,7 @@

      Flag Package: {{ package.pkgname }}

      Note that all of the following packages will be marked out of date:

        {% for pkg in packages %} -
      • {{ pkg.pkgname }} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})
      • +
      • {% pkg_details_link pkg %} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})
      • {% endfor %}
      diff --git a/templates/packages/flag_confirmed.html b/templates/packages/flag_confirmed.html index 9ef316cb..398212f8 100644 --- a/templates/packages/flag_confirmed.html +++ b/templates/packages/flag_confirmed.html @@ -1,4 +1,6 @@ {% extends "base.html" %} +{% load package_extras %} + {% block title %}Arch Linux - Package Flagged - {{ package.pkgname }}{% endblock %} {% block navbarclass %}anb-packages{% endblock %} @@ -9,12 +11,10 @@

      Package Flagged - {{ package.pkgname }}

      Thank you, the maintainers have been notified the following packages are out-of-date:

        {% for pkg in packages %} -
      • {{ pkg.pkgname }} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})
      • +
      • {% pkg_details_link pkg %} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})
      • {% endfor %}
      -

      You can return to the package details page for - {{package.pkgname}}.

      +

      You can return to the package details page for {% pkg_details_link package %}.

      {% endblock %} diff --git a/templates/packages/flagged.html b/templates/packages/flagged.html index 3a39d178..a99a6924 100644 --- a/templates/packages/flagged.html +++ b/templates/packages/flagged.html @@ -1,16 +1,15 @@ {% extends "base.html" %} +{% load package_extras %} + {% block title %}Arch Linux - Flag Package - {{ pkg.pkgname }}{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% block content %}
      -

      Error: Package already flagged

      {{pkg.pkgname}} has already been flagged out-of-date.

      -

      You can return to the package details page for - {{pkg.pkgname}}.

      - +

      You can return to the package details page for {% pkg_details_link pkg %}.

      {% endblock %} diff --git a/templates/packages/packages_list.html b/templates/packages/packages_list.html index ccc091d8..942e1073 100644 --- a/templates/packages/packages_list.html +++ b/templates/packages/packages_list.html @@ -1,4 +1,6 @@ {% extends "base.html" %} +{% load package_extras %} + {% block title %}Arch Linux - {{ name }} ({{ arch.name }}) - {{ list_title }}{% endblock %} {% block navbarclass %}anb-packages{% endblock %} @@ -23,8 +25,7 @@

      {{ list_title }} - {{ name }} ({{ arch.name }})

      {{ pkg.arch.name }} {{ pkg.repo.name|capfirst }} - {{ pkg.pkgname }} + {% pkg_details_link pkg %} {% if pkg.flag_date %} {{ pkg.full_version }} {% else %} diff --git a/templates/packages/search.html b/templates/packages/search.html index 381ebb01..eb4aceca 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -104,8 +104,7 @@

      Package Search

      {% endif %} {{ pkg.arch.name }} {{ pkg.repo.name|capfirst }} - {{ pkg.pkgname }} + {% pkg_details_link pkg %} {% if pkg.flag_date %} {{ pkg.full_version }} {% else %} diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 6014396c..baf85338 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -1,4 +1,6 @@ {% extends "base.html" %} +{% load package_extras %} + {% block title %}Arch Linux - Package Signoffs{% endblock %} {% block navbarclass %}anb-packages{% endblock %} @@ -28,8 +30,7 @@

      Package Signoffs

      {% with group.package as pkg %} {{ pkg.arch.name }} - {{ pkg.pkgname }} + {% pkg_details_link pkg %} {{ group.packages|length }} {{ pkg.full_version }} {{ pkg.last_update|date }} -- cgit v1.2.3-54-g00ecf From b158cdedb5510becc39cc4ab9baf6eddb38c8389 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 9 Aug 2011 23:36:04 -0500 Subject: Link package provisions if available Rather than just the blank 'virtual' text we used to have in the dependencies listing on the package page. Signed-off-by: Dan McGee --- templates/packages/details.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'templates/packages') diff --git a/templates/packages/details.html b/templates/packages/details.html index 8e3c0022..a3f2cef4 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -158,7 +158,10 @@

        {% for depend in deps %} {% ifequal depend.pkg None %} -
      • {{ depend.dep.depname }} (virtual)
      • + {% if depend.providers %} +
      • {{ depend.dep.depname }} ({% multi_pkg_details depend.providers %})
      • + {% else %}
      • {{ depend.dep.depname }} (virtual)
      • + {% endif %} {% else %}
      • {% pkg_details_link depend.pkg %}{{ depend.dep.depvcmp|default:"" }} {% if depend.pkg.repo.testing %}(testing){% endif %} -- cgit v1.2.3-54-g00ecf From 0df3567ae25bb2856bc62951844d9dab5ea97990 Mon Sep 17 00:00:00 2001 From: Sergej Pupykin Date: Sat, 13 Aug 2011 00:23:36 +0400 Subject: add "search wiki" link to package details page Dan: fix usage of urlencode() function. Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 8 ++++++++ templates/packages/details.html | 1 + 2 files changed, 9 insertions(+) (limited to 'templates/packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index 7bc868de..e4c7a010 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -72,6 +72,14 @@ def svn_arch(package): def svn_trunk(package): return svn_link(package, "trunk") +@register.simple_tag +def get_wiki_link(package): + data = { + 'search': package.pkgname, + } + return "https://wiki.archlinux.org/index.php/Special:Search?%s" % \ + urlencode(data) + @register.simple_tag def bugs_list(package): data = { diff --git a/templates/packages/details.html b/templates/packages/details.html index a3f2cef4..bec4bdff 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -18,6 +18,7 @@

        Package Actions

        • SVN Entries ({{pkg.repo|lower}}-{{pkg.arch}})
        • SVN Entries (trunk)
        • +
        • Search Wiki
        • Bug Reports
        • Report a Bug
        • {% if pkg.flag_date %} -- cgit v1.2.3-54-g00ecf From 8e1cae30596e48bfcc958dcd840c09b4cb8987ba Mon Sep 17 00:00:00 2001 From: Thomas Bächler Date: Sun, 14 Aug 2011 16:32:35 +0200 Subject: templates/flag.html: Improve the note about bug reports. There are tons of morons out there who are not able to read. Thus, I keep getting bug reports via the "flag out of date" feature. This patch removes all occurrences of "please" from the note and adds a new sentence that explains our ignorance properly. The hint to file a bug report is now longer, so idiots might see it. Dan: add one more line in the early text. Signed-off-by: Dan McGee --- templates/packages/flag.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/flag.html b/templates/packages/flag.html index 261d6066..34dfe34e 100644 --- a/templates/packages/flag.html +++ b/templates/packages/flag.html @@ -10,7 +10,7 @@

          Flag Package: {{ package.pkgname }}

          If you notice a package is out-of-date (i.e., there is a newer stable release available), then please notify us using - the form below.

          + the form below. Do not report bugs via this form!

          Note that all of the following packages will be marked out of date:

            @@ -26,9 +26,10 @@

            Flag Package: {{ package.pkgname }}

            title="Visit the arch-general mailing list">arch-general mailing list with your additional text.

            -

            Note: Please do not use this facility if the - package is broken! Please file a bug instead.

            +

            Note: Do not use this facility if the + package is broken! The package will be unflagged and the report will be ignored! + Use the + bugtracker to file a bug instead.

            Please confirm your flag request for {{package.pkgname}}:

            -- cgit v1.2.3-54-g00ecf From a06ab5c1eb2053d33e15244ea579875aa72c0c1f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 16 Aug 2011 15:35:59 -0500 Subject: Tabs -> spaces in templates Signed-off-by: Dan McGee --- templates/packages/search.html | 12 ++++++------ templates/public/about.html | 2 +- templates/public/index.html | 4 ++-- templates/public/svn.html | 18 +++++++++--------- templates/registration/logout.html | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/search.html b/templates/packages/search.html index eb4aceca..8a357024 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -153,12 +153,12 @@

            Package Search

            {% else %}
            -

            We couldn't find any packages matching your query. Try searching again - using different criteria, or try - {% if search_form.q.data %} - searching the AUR - {% else %}searching the AUR{% endif %} - to see if the package can be found there.

            +

            We couldn't find any packages matching your query. Try searching again + using different criteria, or try + {% if search_form.q.data %} + searching the AUR + {% else %}searching the AUR{% endif %} + to see if the package can be found there.

            {% endif %} diff --git a/templates/public/about.html b/templates/public/about.html index 0e0601e8..bf38e8a1 100644 --- a/templates/public/about.html +++ b/templates/public/about.html @@ -3,7 +3,7 @@ {% block content %}

            About Arch Linux

            -

            +

            Arch Linux is an independently developed, i686/x86-64 general purpose GNU/Linux distribution versatile enough to suit any role. Development focuses on simplicity, minimalism, and code elegance. Arch is installed as a diff --git a/templates/public/index.html b/templates/public/index.html index add921d1..e68943c8 100644 --- a/templates/public/index.html +++ b/templates/public/index.html @@ -82,7 +82,7 @@

            Recent Updates ( {{ update.pkgbase }} {{ update.version }} - + {% for pkg in update.package_links %}{{ pkg.arch }}{% if not forloop.last %}/{% endif %}{% endfor %} @@ -161,7 +161,7 @@

            Development

            title="Developer Wiki articles">DeveloperWiki
          • Todo Lists
          • -
          • Releng Testbuild Feedback
          diff --git a/templates/public/svn.html b/templates/public/svn.html index dd175bcc..aedd9a73 100644 --- a/templates/public/svn.html +++ b/templates/public/svn.html @@ -7,25 +7,25 @@

          SVN Repositories

          The PKGBUILD files can be fetched via the ABS utility. To learn more about ABS, see the ABS wiki page.

          -

          The SVN repositories have been cloned into git repositories and can be - viewed via the cgit interface. - All - packages are available here except for - community - and multilib which are available in a different repository.

          +

          The SVN repositories have been cloned into git repositories and can be + viewed via the cgit interface. + All + packages are available here except for + community + and multilib which are available in a different repository.

          You can also get individual PKGBUILDs directly from SVN. This can be especially useful if you need to compile an older version of a package. DO NOT CHECK OUT THE ENTIRE SVN REPO. Your address may be blocked. Use the following commands to check out a specific package: -

          +

          svn checkout --depth=empty svn://svn.archlinux.org/packages
           cd packages
           svn update <your-package-name>
          - For the community and multilib repositories, use the following commands - instead: + For the community and multilib repositories, use the following commands + instead:
          svn checkout --depth=empty svn://svn.archlinux.org/community
           cd community
           svn update <your-package-name>
          diff --git a/templates/registration/logout.html b/templates/registration/logout.html index e890ce99..50b3574b 100644 --- a/templates/registration/logout.html +++ b/templates/registration/logout.html @@ -5,7 +5,7 @@

          Developer Logout

          -

          Logout was successful.

          +

          Logout was successful.

          {% endblock %} -- cgit v1.2.3-54-g00ecf From a52c2744bf3b532f3f02ce45ae9d902706f9f518 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 17 Aug 2011 16:16:32 -0500 Subject: Add capfirst filter to yesno usage in templates Signed-off-by: Dan McGee --- templates/mirrors/mirror_details.html | 10 +++++----- templates/mirrors/mirrors.html | 6 +++--- templates/packages/signoffs.html | 4 ++-- templates/releng/iso_overview.html | 12 +++--------- templates/releng/result_list.html | 2 +- 5 files changed, 14 insertions(+), 20 deletions(-) (limited to 'templates/packages') diff --git a/templates/mirrors/mirror_details.html b/templates/mirrors/mirror_details.html index 1795d0f5..3daf1a2d 100644 --- a/templates/mirrors/mirror_details.html +++ b/templates/mirrors/mirror_details.html @@ -24,16 +24,16 @@

          Mirror Details: {{ mirror.name }}

          Has ISOs: - {{ mirror.isos|yesno }} + {{ mirror.isos|yesno|capfirst }} {% if user.is_authenticated %} Public: - {{ mirror.public|yesno }} + {{ mirror.public|yesno|capfirst }} Active: - {{ mirror.active|yesno }} + {{ mirror.active|yesno|capfirst }} Rsync IPs: @@ -91,8 +91,8 @@

          Available URLs

          {% for m_url in urls %} {% if m_url.protocol.is_download %}{{ m_url.url }}{% else %}{{ m_url.url }}{% endif %} - {{ m_url.has_ipv4|yesno }} - {{ m_url.has_ipv6|yesno }} + {{ m_url.has_ipv4|yesno|capfirst }} + {{ m_url.has_ipv6|yesno|capfirst }} {{ m_url.last_sync|date:'Y-m-d H:i'|default:'unknown' }} {{ m_url.completion_pct|percentage:1 }} {{ m_url.delay|duration|default:'unknown' }} diff --git a/templates/mirrors/mirrors.html b/templates/mirrors/mirrors.html index ee76acbe..bf356080 100644 --- a/templates/mirrors/mirrors.html +++ b/templates/mirrors/mirrors.html @@ -27,11 +27,11 @@

          Mirror Overview

          title="Mirror details for {{ mirror.name }}">{{ mirror.name }} {{mirror.get_tier_display}} {{mirror.country}} - {{mirror.isos|yesno}} + {{mirror.isos|yesno|capfirst}} {{mirror.supported_protocols|join:", "}} {% if user.is_authenticated %} - {{mirror.public|yesno}} - {{mirror.active|yesno}} + {{mirror.public|yesno|capfirst}} + {{mirror.active|yesno|capfirst}} {{mirror.admin_email}} {{mirror.notes|linebreaks}} {% endif %} diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index baf85338..a8aa4de2 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -35,8 +35,8 @@

          Package Signoffs

          {{ pkg.full_version }} {{ pkg.last_update|date }} {{ group.target_repo }} - - {{ group.approved|yesno:"Yes,No" }} + + {{ group.approved|yesno|capfirst }}

      {{ iso.name }} - - {{ iso.active|yesno }} - - - {{ iso.successes }} - - - {{ iso.failures }} - + {{ iso.active|yesno|capfirst }} + {{ iso.successes }} + {{ iso.failures }} {% endfor %} diff --git a/templates/releng/result_list.html b/templates/releng/result_list.html index a343257e..7f9ed452 100644 --- a/templates/releng/result_list.html +++ b/templates/releng/result_list.html @@ -26,7 +26,7 @@

      Results for: {{ test.user_name }} {{ test.created|date }} {{ test.architecture }} - {{ test.success|yesno }} + {{ test.success|yesno|capfirst }} {% endfor %} -- cgit v1.2.3-54-g00ecf From e5d09fb7e9003b7f96685af9c0a722b45746448e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 17 Aug 2011 16:18:12 -0500 Subject: Add PGP signature package field And add eventual display code for it to the details template, but don't show it yet as no packages will have it. Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 1 + .../0053_auto__add_field_package_pgp_signature.py | 152 +++++++++++++++++++++ main/models.py | 6 +- templates/packages/details.html | 5 + 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 main/migrations/0053_auto__add_field_package_pgp_signature.py (limited to 'templates/packages') diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 97fdbb73..cf597577 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -203,6 +203,7 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): dbpkg.packager_str = repopkg.packager # attempt to find the corresponding django user for this string dbpkg.packager = finder.find(repopkg.packager) + dbpkg.pgp_signature = repopkg.pgpsig if timestamp: dbpkg.flag_date = None diff --git a/main/migrations/0053_auto__add_field_package_pgp_signature.py b/main/migrations/0053_auto__add_field_package_pgp_signature.py new file mode 100644 index 00000000..a828d1ef --- /dev/null +++ b/main/migrations/0053_auto__add_field_package_pgp_signature.py @@ -0,0 +1,152 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.add_column('packages', 'pgp_signature', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) + + def backwards(self, orm): + db.delete_column('packages', 'pgp_signature') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "['name']", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.donor': { + 'Meta': {'ordering': "['name']", 'object_name': 'Donor', 'db_table': "'donors'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.packagedepend': { + 'Meta': {'object_name': 'PackageDepend', 'db_table': "'package_depends'"}, + 'depname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'depvcmp': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.packagefile': { + 'Meta': {'object_name': 'PackageFile', 'db_table': "'package_files'"}, + 'directory': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_directory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.repo': { + 'Meta': {'ordering': "['name']", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'main.todolist': { + 'Meta': {'object_name': 'Todolist', 'db_table': "'todolists'"}, + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'main.todolistpkg': { + 'Meta': {'unique_together': "(('list', 'pkg'),)", 'object_name': 'TodolistPkg', 'db_table': "'todolist_pkgs'"}, + 'complete': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Todolist']"}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'user_profiles'"}, + 'alias': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'allowed_repos': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Repo']", 'symmetrical': 'False', 'blank': 'True'}), + 'favorite_distros': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'interests': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'languages': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'other_contact': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'pgp_key': ('main.models.PGPKeyField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'picture': ('django.db.models.fields.files.FileField', [], {'default': "'devs/silhouette.png'", 'max_length': '100'}), + 'public_email': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'roles': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'time_zone': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'userprofile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'website': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'yob': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['main'] diff --git a/main/models.py b/main/models.py index 0125cc0e..e0681abf 100644 --- a/main/models.py +++ b/main/models.py @@ -82,6 +82,7 @@ class UserProfile(models.Model): help_text="Ideally 125px by 125px") user = models.OneToOneField(User, related_name='userprofile') allowed_repos = models.ManyToManyField('Repo', blank=True) + class Meta: db_table = 'user_profiles' verbose_name = 'Additional Profile Data' @@ -173,6 +174,7 @@ class Package(models.Model): packager_str = models.CharField(max_length=255) packager = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) + pgp_signature = models.TextField(null=True, blank=True) flag_date = models.DateTimeField(null=True) objects = PackageManager() @@ -199,6 +201,9 @@ def get_full_url(self, proto='http'): domain = Site.objects.get_current().domain return '%s://%s%s' % (proto, domain, self.get_absolute_url()) + def is_signed(self): + return bool(self.pgp_signature) + @property def maintainers(self): return User.objects.filter( @@ -288,7 +293,6 @@ def get_depends(self): if not pkg: providers = dep.get_providers(arches, testing=self.repo.testing, staging=self.repo.staging) - print providers deps.append({'dep': dep, 'pkg': pkg, 'providers': providers}) return deps diff --git a/templates/packages/details.html b/templates/packages/details.html index bec4bdff..2f1031a6 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -138,6 +138,11 @@

      Versions Elsewhere

      Last Packager: {% with pkg.packager as pkgr %}{% if pkgr %}{% userpkgs pkgr %}{% else %}{{ pkg.packager_str }}{% endif %}{% endwith %} + + {% comment %} + Signed: + {{ pkg.is_signed|yesno|capfirst }} + {% endcomment %} Build Date: {{ pkg.build_date|date:"DATETIME_FORMAT" }} UTC -- cgit v1.2.3-54-g00ecf From 60738969375fc72ff254517107ce53e14b068e24 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 19 Aug 2011 18:36:47 -0500 Subject: Fix template comment guard Signed-off-by: Dan McGee --- templates/packages/details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'templates/packages') diff --git a/templates/packages/details.html b/templates/packages/details.html index 2f1031a6..d09d8ada 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -142,8 +142,8 @@

      Versions Elsewhere

      {% comment %} Signed: {{ pkg.is_signed|yesno|capfirst }} - {% endcomment %} + {% endcomment %} Build Date: {{ pkg.build_date|date:"DATETIME_FORMAT" }} UTC -- cgit v1.2.3-54-g00ecf From e4b75dc124a53090b429bafbc3621c0e0a0f6247 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 25 Aug 2011 11:10:56 -0500 Subject: Enable display of package signed status Signed-off-by: Dan McGee --- templates/packages/details.html | 2 -- 1 file changed, 2 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/details.html b/templates/packages/details.html index d09d8ada..afbf9103 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -139,11 +139,9 @@

      Versions Elsewhere

      Last Packager: {% with pkg.packager as pkgr %}{% if pkgr %}{% userpkgs pkgr %}{% else %}{{ pkg.packager_str }}{% endif %}{% endwith %} - {% comment %} Signed: {{ pkg.is_signed|yesno|capfirst }} - {% endcomment %} Build Date: {{ pkg.build_date|date:"DATETIME_FORMAT" }} UTC -- cgit v1.2.3-54-g00ecf From 2ed3676f61af821e71a5c070e65419cd60906cb8 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 23 Aug 2011 17:17:30 -0500 Subject: Escape parameter to search AUR link Addresses FS#25732. Signed-off-by: Dan McGee --- templates/packages/search.html | 2 +- templates/public/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/search.html b/templates/packages/search.html index 8a357024..4744aa88 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -156,7 +156,7 @@

      Package Search

      We couldn't find any packages matching your query. Try searching again using different criteria, or try {% if search_form.q.data %} - searching the AUR + searching the AUR {% else %}searching the AUR{% endif %} to see if the package can be found there.

      diff --git a/templates/public/index.html b/templates/public/index.html index c8b6def0..bea19e0f 100644 --- a/templates/public/index.html +++ b/templates/public/index.html @@ -20,7 +20,7 @@

      A simple, lightweight distribution

      Currently we have official packages optimized for the i686 and x86-64 architectures. We complement our official package sets with a - + community-operated package repository that grows in size and quality each and every day.

      -- cgit v1.2.3-54-g00ecf From 25a15d4c570823c6e28693d68d57b803dc2673fa Mon Sep 17 00:00:00 2001 From: Evangelos Foutras Date: Thu, 1 Sep 2011 19:56:05 +0300 Subject: Use package branches to display commit history We now have one link pointing to the tree of /trunk, and another pointing to the log of /trunk. Both links specify a package branch. Signed-off-by: Evangelos Foutras Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 19 ++++++------------- templates/packages/details.html | 6 ++++-- 2 files changed, 10 insertions(+), 15 deletions(-) (limited to 'templates/packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index 42001aa5..01bf7510 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -61,20 +61,13 @@ def userpkgs(user): ) return '' -def svn_link(package, svnpath): - '''Helper function for the two real SVN link methods.''' - parts = (package.repo.svn_root, package.pkgbase, svnpath) - linkbase = "http://projects.archlinux.org/svntogit/%s.git/tree/%s/%s/" - return linkbase % tuple(urlquote(part) for part in parts) - @register.simple_tag -def svn_arch(package): - repo = package.repo.name.lower() - return svn_link(package, "repos/%s-%s" % (repo, package.arch.name)) - -@register.simple_tag -def svn_trunk(package): - return svn_link(package, "trunk") +def scm_link(package, operation): + parts = (package.repo.svn_root, operation, package.pkgbase) + linkbase = ( + "http://projects.archlinux.org/svntogit/%s.git/%s/trunk?" + "h=packages/%s") + return linkbase % tuple(urlquote(part) for part in parts) @register.simple_tag def get_wiki_link(package): diff --git a/templates/packages/details.html b/templates/packages/details.html index afbf9103..fa8283ed 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -16,8 +16,10 @@

      Package Details: {{ pkg.pkgname }} {{ pkg.full_version }}

      Package Actions

        -
      • SVN Entries ({{pkg.repo|lower}}-{{pkg.arch}})
      • -
      • SVN Entries (trunk)
      • +
      • + Source Files / + View Changes +
      • Search Wiki
      • Bug Reports
      • Report a Bug
      • -- cgit v1.2.3-54-g00ecf From 6aaa9119c55503f382cecbef72532db87a87c782 Mon Sep 17 00:00:00 2001 From: Evangelos Foutras Date: Thu, 1 Sep 2011 19:56:06 +0300 Subject: Compact bug report links in "Package Actions" box Using the same style as the SCM links. Signed-off-by: Evangelos Foutras Signed-off-by: Dan McGee --- templates/packages/details.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/details.html b/templates/packages/details.html index fa8283ed..76d3ce86 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -20,9 +20,11 @@

        Package Actions

        Source Files / View Changes +
      • + Bug Reports / + Add New Bug +
      • Search Wiki
      • -
      • Bug Reports
      • -
      • Report a Bug
      • {% if pkg.flag_date %}
      • Flagged out-of-date on {{ pkg.flag_date|date }}
      • {% with pkg.in_testing as tp %}{% if tp %} -- cgit v1.2.3-54-g00ecf From f173a03857e92268412196cd8e7c0f5d27fb6a38 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 8 Sep 2011 14:09:45 -0500 Subject: Show provides in package details view Signed-off-by: Dan McGee --- templates/packages/details.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'templates/packages') diff --git a/templates/packages/details.html b/templates/packages/details.html index 76d3ce86..8be408fb 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -123,7 +123,16 @@

        Versions Elsewhere

        {% else %}None{% endif %} {% endwith %} - + + {% with pkg.provides.all as provides %} + {% if provides %} + + Provides: + {% for p in provides %}{{ p.name }}{% if p.version %}={{ p.version }}{% endif %}
        {% endfor %} + + {% endif %} + {% endwith %} + Maintainers: {% with pkg.maintainers as maints %} {% if maints %} -- cgit v1.2.3-54-g00ecf From 293e42fc55ca0ee8f67d5bdcb3d32fd0ec93786e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 8 Sep 2011 14:10:13 -0500 Subject: List packages in required by list with depend provided by this package Implements FS#25862. Most noticable on package like util-linux (util-linux-ng) and openjdk6 (java-runtime, java-environment). Signed-off-by: Dan McGee --- main/models.py | 4 +++- templates/packages/details.html | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'templates/packages') diff --git a/main/models.py b/main/models.py index e0681abf..1efca78d 100644 --- a/main/models.py +++ b/main/models.py @@ -234,9 +234,11 @@ def get_requiredby(self): list slim by including the corresponding package in the same testing category as this package if that check makes sense. """ + provides = set(self.provides.values_list('name', flat=True)) + provides.add(self.pkgname) requiredby = PackageDepend.objects.select_related('pkg', 'pkg__arch', 'pkg__repo').filter( - depname=self.pkgname).order_by( + depname__in=provides).order_by( 'pkg__pkgname', 'pkg__arch__name', 'pkg__repo__name') if not self.arch.agnostic: # make sure we match architectures if possible diff --git a/templates/packages/details.html b/templates/packages/details.html index 8be408fb..1016b43a 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -203,6 +203,7 @@

          {% for req in rqdby %}
        • {% pkg_details_link req.pkg %} + {% if req.depname != pkg.pkgname %}(requires {{ req.depname }}){% endif %} {% if req.pkg.repo.testing %}(testing){% endif %} {% if req.optional %}(optional){% endif %}
        • -- cgit v1.2.3-54-g00ecf From fa38fa74d4d4186647b6e8f5c0e9661ad65d2957 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 18 Oct 2011 10:55:06 -0500 Subject: Add architecture to package details and files page titles And remove the not totally necessary "Package Details" text as that seems like a reasonable assumption for the base page. Signed-off-by: Dan McGee --- templates/packages/details.html | 2 +- templates/packages/files.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/details.html b/templates/packages/details.html index 1016b43a..2998592f 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -2,7 +2,7 @@ {% load cache %} {% load package_extras %} -{% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.full_version }} - Package Details{% endblock %} +{% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.full_version }} ({{ pkg.arch.name }}){% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% load package_extras %} diff --git a/templates/packages/files.html b/templates/packages/files.html index 362e62cd..50ab6e83 100644 --- a/templates/packages/files.html +++ b/templates/packages/files.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.full_version }} - Package File List{% endblock %} +{% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.full_version }} ({{ pkg.arch.name }}) - File List{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% block content %} -- cgit v1.2.3-54-g00ecf From 23020b33a0f8bf48c76d82726f143a09fca0fd1f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 18 Oct 2011 10:59:14 -0500 Subject: Refresh title and other aspects of flag package templates * Update to contain version and architecture * Update some page text to be more descriptive * Add a meta tag to not directly index these pages in search engines Signed-off-by: Dan McGee <dan@archlinux.org> --- templates/packages/flag.html | 7 ++++--- templates/packages/flag_confirmed.html | 6 ++++-- templates/packages/flagged.html | 5 +++-- 3 files changed, 11 insertions(+), 7 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/flag.html b/templates/packages/flag.html index 34dfe34e..5fc9c91d 100644 --- a/templates/packages/flag.html +++ b/templates/packages/flag.html @@ -1,18 +1,19 @@ {% extends "base.html" %} {% load package_extras %} -{% block title %}Arch Linux - Flag Package - {{ package.pkgname }}{% endblock %} +{% block title %}Arch Linux - Flag Package - {{ package.pkgname }} {{ package.full_version }} ({{ package.arch.name }}){% endblock %} +{% block head %}<meta name="robots" content="noindex"/>{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% block content %} <div id="pkg-flag" class="box"> - <h2>Flag Package: {{ package.pkgname }}</h2> + <h2>Flag Package: {{ package.pkgname }} {{ package.full_version }} ({{ package.arch.name }})</h2> <p>If you notice a package is out-of-date (i.e., there is a newer <strong>stable</strong> release available), then please notify us using the form below. Do <em>not</em> report bugs via this form!</p> - <p>Note that all of the following packages will be marked out of date:</p> + <p>Note that the following {{ packages|length }} package{{ packages|pluralize }} will be marked out of date:</p> <ul> {% for pkg in packages %} <li>{% pkg_details_link pkg %} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})</li> diff --git a/templates/packages/flag_confirmed.html b/templates/packages/flag_confirmed.html index 398212f8..6274adbb 100644 --- a/templates/packages/flag_confirmed.html +++ b/templates/packages/flag_confirmed.html @@ -1,14 +1,16 @@ {% extends "base.html" %} {% load package_extras %} -{% block title %}Arch Linux - Package Flagged - {{ package.pkgname }}{% endblock %} +{% block title %}Arch Linux - Package Flagged - {{ package.pkgname }} {{ package.full_version }} ({{ package.arch.name }}){% endblock %} +{% block head %}<meta name="robots" content="noindex"/>{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% block content %} <div id="pkg-flag" class="box"> <h2>Package Flagged - {{ package.pkgname }}</h2> - <p>Thank you, the maintainers have been notified the following packages are out-of-date:</p> + <p>Thank you, the maintainers have been notified the following + {{ packages|length }} package{{ packages|pluralize }} are out-of-date:</p> <ul> {% for pkg in packages %} <li>{% pkg_details_link pkg %} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})</li> diff --git a/templates/packages/flagged.html b/templates/packages/flagged.html index a99a6924..bbe0fad5 100644 --- a/templates/packages/flagged.html +++ b/templates/packages/flagged.html @@ -1,12 +1,13 @@ {% extends "base.html" %} {% load package_extras %} -{% block title %}Arch Linux - Flag Package - {{ pkg.pkgname }}{% endblock %} +{% block title %}Arch Linux - Flag Package - {{ pkg.pkgname }} {{ pkg.full_version }} ({{ pkg.arch.name }}){% endblock %} +{% block head %}<meta name="robots" content="noindex"/>{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% block content %} <div id="pkg-flagged-error" class="box"> - <h2>Error: Package already flagged</h2> + <h2>Package {{ pkg.pkgname }} {{ pkg.full_version }} ({{ pkg.arch.name }}) already flagged</h2> <p><strong>{{pkg.pkgname}}</strong> has already been flagged out-of-date.</p> -- cgit v1.2.3-54-g00ecf From e31d7f864ddfbce49eda91aa01654b76dcd009b9 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Wed, 19 Oct 2011 11:04:42 -0500 Subject: Use admin_media_prefix helper everywhere in search template Signed-off-by: Dan McGee <dan@archlinux.org> --- templates/packages/search.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'templates/packages') diff --git a/templates/packages/search.html b/templates/packages/search.html index 4744aa88..800b883d 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -1,10 +1,12 @@ {% extends "base.html" %} {% load package_extras %} +{% load adminmedia %} + {% block title %}Arch Linux - Package Database{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% block head %} -<link rel="stylesheet" type="text/css" href="/media/admin_media/css/widgets.css" /> +<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/widgets.css" /> {% endblock %} {% block content %} @@ -170,7 +172,7 @@ <h3>Package Search</h3> </div> <script type="text/javascript" src="/jsi18n/"></script> -{% load adminmedia %}<script type="text/javascript" src="{% admin_media_prefix %}js/core.js"></script> <script type="text/javascript">window.__admin_media_prefix__ = "{% filter escapejs %}{% admin_media_prefix %}{% endfilter %}";</script> +<script type="text/javascript" src="{% admin_media_prefix %}js/core.js"></script> {{search_form.media}} {% endblock %} -- cgit v1.2.3-54-g00ecf From ac2278423a3d449fdfe8c813f1f2d391ef9aff08 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 3 Nov 2011 14:59:00 -0500 Subject: Many signoff page improvements Add a new 'SignoffSpecification' model which will capture metadata regarding a specific package if it differs from the norm- e.g. more or less than 2 required signoffs, is known to be bad, a comment from the maintainer, etc. The groundwork is laid here; much of this will still need to be wired up in the future. Enhance the view with a lot more JS prettiness and add revoking of signoffs. The signoff page can be filtered and the links and all the fun stuff are totally dynamic now. Signed-off-by: Dan McGee <dan@archlinux.org> --- main/models.py | 11 -- media/archweb.css | 1 - media/archweb.js | 72 ++++++-- .../0010_auto__add_signoffspecification.py | 183 +++++++++++++++++++++ packages/models.py | 45 ++++- packages/urls.py | 1 + packages/views.py | 71 +++++--- templates/packages/differences.html | 2 +- templates/packages/signoff_cell.html | 12 ++ templates/packages/signoffs.html | 50 +++--- 10 files changed, 379 insertions(+), 69 deletions(-) create mode 100644 packages/migrations/0010_auto__add_signoffspecification.py create mode 100644 templates/packages/signoff_cell.html (limited to 'templates/packages') diff --git a/main/models.py b/main/models.py index 780453c0..d55a9673 100644 --- a/main/models.py +++ b/main/models.py @@ -7,7 +7,6 @@ from main.utils import cache_function, make_choice, set_created_field from packages.models import PackageRelation -from packages.models import Signoff as PackageSignoff from datetime import datetime from itertools import groupby @@ -213,16 +212,6 @@ def maintainers(self): package_relations__pkgbase=self.pkgbase, package_relations__type=PackageRelation.MAINTAINER) - @property - def signoffs(self): - return PackageSignoff.objects.select_related('user').filter( - pkgbase=self.pkgbase, pkgver=self.pkgver, pkgrel=self.pkgrel, - epoch=self.epoch, arch=self.arch, repo=self.repo) - - def approved_for_signoff(self): - count = self.signoffs.filter(revoked__isnull=True).count() - return count >= PackageSignoff.REQUIRED - @cache_function(300) def applicable_arches(self): '''The list of (this arch) + (available agnostic arches).''' diff --git a/media/archweb.css b/media/archweb.css index ea2f3fb5..62dc4fbc 100644 --- a/media/archweb.css +++ b/media/archweb.css @@ -912,7 +912,6 @@ ul.admin-actions { #dev-signoffs .signed-username { color: #888; - margin-left: 0.5em; } /* iso testing feedback form */ diff --git a/media/archweb.js b/media/archweb.js index a51ae460..43812b33 100644 --- a/media/archweb.js +++ b/media/archweb.js @@ -139,7 +139,7 @@ function ajaxifyFiles() { /* packages/differences.html */ function filter_packages() { - // start with all rows, and then remove ones we shouldn't show + /* start with all rows, and then remove ones we shouldn't show */ var rows = $('#tbody_differences').children(); var all_rows = rows; if (!$('#id_multilib').is(':checked')) { @@ -150,12 +150,12 @@ function filter_packages() { rows = rows.filter('.' + arch); } if (!$('#id_minor').is(':checked')) { - // this check is done last because it is the most expensive + /* this check is done last because it is the most expensive */ var pat = /(.*)-(.+)/; rows = rows.filter(function(index) { var cells = $(this).children('td'); - // all this just to get the split version out of the table cell + /* all this just to get the split version out of the table cell */ var ver_a = cells.eq(2).find('span').text().match(pat); if (!ver_a) { return true; @@ -166,26 +166,26 @@ function filter_packages() { return true; } - // first check pkgver + /* first check pkgver */ if (ver_a[1] !== ver_b[1]) { return true; } - // pkgver matched, so see if rounded pkgrel matches + /* pkgver matched, so see if rounded pkgrel matches */ if (Math.floor(parseFloat(ver_a[2])) === Math.floor(parseFloat(ver_b[2]))) { return false; } - // pkgrel didn't match, so keep the row + /* pkgrel didn't match, so keep the row */ return true; }); } - // hide all rows, then show the set we care about + /* hide all rows, then show the set we care about */ all_rows.hide(); rows.show(); - // make sure we update the odd/even styling from sorting + /* make sure we update the odd/even styling from sorting */ $('.results').trigger('applyWidgets'); } -function filter_reset() { +function filter_packages_reset() { $('#id_archonly').val('both'); $('#id_multilib').removeAttr('checked'); $('#id_minor').removeAttr('checked'); @@ -213,26 +213,72 @@ function todolist_flag() { function signoff_package() { var link = this; $.getJSON(link.href, function(data) { + link = $(link); + var signoff = null; if (data.created) { - var signoff = $('<li>').addClass('signed-username').text(data.user); - $(link).append(signoff); + signoff = $('<li>').addClass('signed-username').text(data.user); + link.closest('td').children('ul').append(signoff); + } else if(data.user) { + signoff = link.closest('td').find('li').filter(function(index) { + return $(this).text() == data.user; + }); + } + console.log(signoff, data.revoked, data.user); + if (signoff && data.revoked) { + signoff.text(signoff.text() + ' (revoked)'); } /* update the approved column to reflect reality */ var approved; if (data.approved) { - approved = $(link).closest('tr').children('.signoff-no'); + approved = link.closest('tr').children('.signoff-no'); approved.text('Yes').addClass( 'signoff-yes').removeClass('signoff-no'); } else { - approved = $(link).closest('tr').children('.signoff-yes'); + approved = link.closest('tr').children('.signoff-yes'); approved.text('No').addClass( 'signoff-no').removeClass('signoff-yes'); } + link.removeAttr('title'); + /* Form our new link. The current will be something like + * '/packages/repo/arch/package/...' */ + var base_href = link.attr('href').split('/').slice(0, 5).join('/'); + if (data.revoked) { + link.text('Signoff'); + link.attr('href', base_href + '/signoff/'); + } else { + link.text('Revoke Signoff'); + link.attr('href', base_href + '/signoff/revoke/'); + } $('.results').trigger('updateCell', approved); }); return false; } +function filter_signoffs() { + /* start with all rows, and then remove ones we shouldn't show */ + var rows = $('#tbody_signoffs').children(); + var all_rows = rows; + $('#signoffs_filter .arch_filter').each(function() { + if (!$(this).is(':checked')) { + console.log($(this).val()); + rows = rows.not('.' + $(this).val()); + } + }); + if ($('#id_pending').is(':checked')) { + rows = rows.has('td.signoff-no'); + } + /* hide all rows, then show the set we care about */ + all_rows.hide(); + rows.show(); + /* make sure we update the odd/even styling from sorting */ + $('.results').trigger('applyWidgets'); +} +function filter_signoffs_reset() { + $('#signoffs_filter .arch_filter').attr('checked', 'checked'); + $('#id_pending').removeAttr('checked'); + filter_signoffs(); +} + /* visualizations */ function format_filesize(size, decimals) { /*var labels = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];*/ diff --git a/packages/migrations/0010_auto__add_signoffspecification.py b/packages/migrations/0010_auto__add_signoffspecification.py new file mode 100644 index 00000000..da24824e --- /dev/null +++ b/packages/migrations/0010_auto__add_signoffspecification.py @@ -0,0 +1,183 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.create_table('packages_signoffspecification', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('pkgbase', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('pkgver', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('pkgrel', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('epoch', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('arch', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Arch'])), + ('repo', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Repo'])), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('created', self.gf('django.db.models.fields.DateTimeField')()), + ('required', self.gf('django.db.models.fields.PositiveIntegerField')(default=2)), + ('enabled', self.gf('django.db.models.fields.BooleanField')(default=True)), + ('known_bad', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('comments', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + )) + db.send_create_signal('packages', ['SignoffSpecification']) + + + def backwards(self, orm): + db.delete_table('packages_signoffspecification') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "['name']", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "['name']", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'packages.conflict': { + 'Meta': {'ordering': "['name']", 'object_name': 'Conflict'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conflicts'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.license': { + 'Meta': {'ordering': "['name']", 'object_name': 'License'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'licenses'", 'to': "orm['main.Package']"}) + }, + 'packages.packagegroup': { + 'Meta': {'object_name': 'PackageGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Package']"}) + }, + 'packages.packagerelation': { + 'Meta': {'unique_together': "(('pkgbase', 'user', 'type'),)", 'object_name': 'PackageRelation'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_relations'", 'to': "orm['auth.User']"}) + }, + 'packages.provision': { + 'Meta': {'ordering': "['name']", 'object_name': 'Provision'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provides'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.replacement': { + 'Meta': {'ordering': "['name']", 'object_name': 'Replacement'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'replaces'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.signoff': { + 'Meta': {'object_name': 'Signoff'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'revoked': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_signoffs'", 'to': "orm['auth.User']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 4cd3b1b5..ad082501 100644 --- a/packages/models.py +++ b/packages/models.py @@ -38,6 +38,49 @@ def __unicode__(self): class Meta: unique_together = (('pkgbase', 'user', 'type'),) +class SignoffSpecification(models.Model): + ''' + A specification for the signoff policy for this particular revision of a + pakcage. The default is requiring two signoffs for a given package. These + are created only if necessary; e.g., if one wanted to override the + required=2 attribute, otherwise a sane default object is used. + ''' + pkgbase = models.CharField(max_length=255, db_index=True) + pkgver = models.CharField(max_length=255) + pkgrel = models.CharField(max_length=255) + epoch = models.PositiveIntegerField(default=0) + arch = models.ForeignKey('main.Arch') + repo = models.ForeignKey('main.Repo') + user = models.ForeignKey(User) + created = models.DateTimeField(editable=False) + required = models.PositiveIntegerField(default=2) + enabled = models.BooleanField(default=True) + known_bad = models.BooleanField(default=False) + comments = models.TextField(null=True, blank=True) + +class SignoffManager(models.Manager): + def get_from_package(self, pkg, user, revoked=False): + '''Utility method to pull all relevant name-version fields from a + package and create a matching signoff.''' + not_revoked = not revoked + return Signoff.objects.get( + pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, + epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo, + revoked__isnull=not_revoked, user=user) + + def get_or_create_from_package(self, pkg, user): + '''Utility method to pull all relevant name-version fields from a + package and create a matching signoff.''' + return Signoff.objects.get_or_create( + pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, + epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo, + revoked=None, user=user) + + def for_package(self, pkg): + return self.select_related('user').filter( + pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, + epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo) + class Signoff(models.Model): ''' A signoff for a package (by pkgbase) at a given point in time. These are @@ -55,7 +98,7 @@ class Signoff(models.Model): revoked = models.DateTimeField(null=True) comments = models.TextField(null=True, blank=True) - REQUIRED = 2 + objects = SignoffManager() @property def packages(self): diff --git a/packages/urls.py b/packages/urls.py index d7d01170..576e3279 100644 --- a/packages/urls.py +++ b/packages/urls.py @@ -10,6 +10,7 @@ (r'^unflag/$', 'unflag'), (r'^unflag/all/$', 'unflag_all'), (r'^signoff/$', 'signoff_package'), + (r'^signoff/revoke/$', 'signoff_package', {'revoke': True}), (r'^download/$', 'download'), ) diff --git a/packages/views.py b/packages/views.py index 5114c87f..035d51cb 100644 --- a/packages/views.py +++ b/packages/views.py @@ -25,7 +25,7 @@ from main.models import Package, PackageFile, Arch, Repo from main.utils import make_choice, groupby_preserve_order, PackageStandin from mirrors.models import MirrorUrl -from .models import PackageRelation, PackageGroup, Signoff +from .models import PackageRelation, PackageGroup, SignoffSpecification, Signoff from .utils import (get_group_info, get_differences_info, get_wrong_permissions, get_current_signoffs) @@ -369,14 +369,24 @@ def unflag_all(request, name, repo, arch): pkgs.update(flag_date=None) return redirect(pkg) +DEFAULT_SIGNOFF_SPEC = SignoffSpecification(required=2) + +def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_SPEC): + if signoffs: + good_signoffs = sum(1 for s in signoffs if not s.revoked) + return good_signoffs >= spec.required + return False + class PackageSignoffGroup(object): '''Encompasses all packages in testing with the same pkgbase.''' - def __init__(self, packages, target_repo=None, signoffs=None): + def __init__(self, packages, user=None): if len(packages) == 0: raise Exception self.packages = packages - self.target_repo = target_repo - self.signoffs = signoffs + self.user = user + self.target_repo = None + self.signoffs = set() + self.specification = DEFAULT_SIGNOFF_SPEC first = packages[0] self.pkgbase = first.pkgbase @@ -406,21 +416,24 @@ def package(self): def find_signoffs(self, all_signoffs): '''Look through a list of Signoff objects for ones matching this particular group and store them on the object.''' - if self.signoffs is None: - self.signoffs = [] for s in all_signoffs: if s.pkgbase != self.pkgbase: continue if self.version and not s.full_version == self.version: continue if s.arch_id == self.arch.id and s.repo_id == self.repo.id: - self.signoffs.append(s) + self.signoffs.add(s) def approved(self): - if self.signoffs: - good_signoffs = [s for s in self.signoffs if not s.revoked] - return len(good_signoffs) >= Signoff.REQUIRED - return False + return approved_by_signoffs(self.signoffs, self.specification) + + def user_signed_off(self, user=None): + '''Did a given user signoff on this package? user can be passed as an + argument, or attached to the group object itself so this can be called + from a template.''' + if user is None: + user = self.user + return user in (s.user for s in self.signoffs if not s.revoked) @permission_required('main.change_package') @never_cache @@ -443,7 +456,7 @@ def signoffs(request): grouped = groupby_preserve_order(packages, same_pkgbase_key) signoff_groups = [] for group in grouped: - signoff_group = PackageSignoffGroup(group) + signoff_group = PackageSignoffGroup(group, user=request.user) signoff_group.target_repo = pkgtorepo.get(signoff_group.pkgbase, "Unknown") signoff_group.find_signoffs(signoffs) @@ -451,27 +464,43 @@ def signoffs(request): signoff_groups.sort(key=attrgetter('pkgbase')) - return direct_to_template(request, 'packages/signoffs.html', - {'signoff_groups': signoff_groups}) + context = { + 'signoff_groups': signoff_groups, + 'arches': Arch.objects.all(), + } + return direct_to_template(request, 'packages/signoffs.html', context) @permission_required('main.change_package') @never_cache -def signoff_package(request, name, repo, arch): +def signoff_package(request, name, repo, arch, revoke=False): packages = get_list_or_404(Package, pkgbase=name, arch__name=arch, repo__name__iexact=repo, repo__testing=True) - pkg = packages[0] - signoff, created = Signoff.objects.get_or_create( - pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, - epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo, user=request.user) + package = packages[0] + + if revoke: + try: + signoff = Signoff.objects.get_from_package( + package, request.user, False) + except Signoff.DoesNotExist: + raise Http404 + signoff.revoked = datetime.utcnow() + signoff.save() + created = False + else: + signoff, created = Signoff.objects.get_or_create_from_package( + package, request.user) + + all_signoffs = Signoff.objects.for_package(package) if request.is_ajax(): data = { 'created': created, - 'approved': pkg.approved_for_signoff(), + 'revoked': bool(signoff.revoked), + 'approved': approved_by_signoffs(all_signoffs), 'user': str(request.user), } - return HttpResponse(simplejson.dumps(data), + return HttpResponse(simplejson.dumps(data, ensure_ascii=False), mimetype='application/json') return redirect('package-signoffs') diff --git a/templates/packages/differences.html b/templates/packages/differences.html index dd1046bc..0400ea37 100644 --- a/templates/packages/differences.html +++ b/templates/packages/differences.html @@ -65,7 +65,7 @@ <h3>Filter Differences View</h3> $('.results').tablesorter({widgets: ['zebra'], sortList: [[1,0], [0,0]]}); $('#diff_filter select').change(filter_packages); $('#diff_filter input').change(filter_packages); - $('#criteria_reset').click(filter_reset); + $('#criteria_reset').click(filter_differences_reset); // fire function on page load to ensure the current form selections take effect filter_packages(); }); diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html new file mode 100644 index 00000000..fce5d551 --- /dev/null +++ b/templates/packages/signoff_cell.html @@ -0,0 +1,12 @@ +<ul> + {% for signoff in group.signoffs %} + <li class="signed-username" title="Signed off by {{ signoff.user }}">{{ signoff.user }}{% if signoff.revoked %} (revoked){% endif %}</li> + {% endfor %} +</ul> +{% if group.user_signed_off %} +<div><a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/revoke/" + title="Revoke signoff {{ group.package.pkgname }} for {{ group.package.arch }}">Revoke Signoff</a></div> +{% else %} +<div><a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/" + title="Signoff {{ group.package.pkgname }} for {{ group.package.arch }}">Signoff</a></div> +{% endif %} diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index a8aa4de2..4a2f6c99 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -12,42 +12,46 @@ <h2>Package Signoffs</h2> <p>{{ signoff_groups|length }} signoff group{{ signoff_groups|pluralize }} found. A "signoff group" consists of packages grouped by pkgbase, architecture, and repository.</p> + <div class="box filter-criteria"> + <h3>Filter Displayed Signoffs</h3> + <form id="signoffs_filter" method="post" action="."> + <fieldset> + <legend>Select filter criteria</legend> + {% for arch in arches %} + <div><label for="id_arch_{{ arch.name }}" title="Architecture {{ arch.name }}">Arch {{ arch.name }}</label> + <input type="checkbox" name="arch_{{ arch.name }}" id="id_arch_{{ arch.name }}" class="arch_filter" value="{{ arch.name }}" checked="checked"/></div> + {% endfor %} + <div><label for="id_pending" title="Packages with not enough signoffs">Only Pending Approval</label> + <input type="checkbox" name="pending" id="id_pending" value="pending"/></div> + <div ><label> </label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div> + </fieldset> + </form> + </div> + <table id="signoffs" class="results"> <thead> <tr> + <th>Package Base/Version</th> <th>Arch</th> - <th>Package Base</th> + <th>Target Repo</th> <th># of Packages</th> - <th>Version</th> <th>Last Updated</th> - <th>Target Repo</th> <th>Approved</th> - <th>Signoff</th> + <th>Signoffs</th> </tr> </thead> - <tbody> + <tbody id="tbody_signoffs"> {% for group in signoff_groups %} {% with group.package as pkg %} - <tr class="{% cycle 'odd' 'even' %}"> + <tr class="{% cycle 'odd' 'even' %} {{ pkg.arch.name }}"> + <td>{% pkg_details_link pkg %} {{ pkg.full_version }}</td> <td>{{ pkg.arch.name }}</td> - <td>{% pkg_details_link pkg %}</td> + <td>{{ group.target_repo }}</td> <td>{{ group.packages|length }}</td> - <td>{{ pkg.full_version }}</td> <td>{{ pkg.last_update|date }}</td> - <td>{{ group.target_repo }}</td> <td class="signoff-{{ group.approved|yesno }}"> {{ group.approved|yesno|capfirst }}</td> - <td> - <ul> - <li><a class="signoff-link" href="{{ pkg.get_absolute_url }}signoff/" - title="Signoff {{ pkg.pkgname }} for {{ pkg.arch }}">Signoff</a> - </li> - {% for signoff in group.signoffs %} - <li class="signed-username" title="Signed off by {{ signoff.user }}"> - {{ signoff.user }}{% if signoff.revoked %} (revoked){% endif %}</li> - {% endfor %} - </ul> - </td> + <td>{% include "packages/signoff_cell.html" %}</td> </tr> {% endwith %} {% endfor %} @@ -60,8 +64,12 @@ <h2>Package Signoffs</h2> <script type="text/javascript"> $(document).ready(function() { $('a.signoff-link').click(signoff_package); - $(".results").tablesorter({widgets: ['zebra'], sortList: [[1,0]], + $(".results").tablesorter({widgets: ['zebra'], sortList: [[0,0]], headers: { 6: { sorter: false } } }); + $('#signoffs_filter input').change(filter_signoffs); + $('#criteria_reset').click(filter_signoffs_reset); + // fire function on page load to ensure the current form selections take effect + filter_signoffs(); }); </script> {% endblock %} -- cgit v1.2.3-54-g00ecf From 74d2a5df5ca7ee4b6497a6e7609491d72cdbb309 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 3 Nov 2011 17:18:13 -0500 Subject: Refactor more package signoff stuff This sets up some shared utility code for use in a later package signoff email report command. Signed-off-by: Dan McGee <dan@archlinux.org> --- packages/models.py | 7 +- packages/utils.py | 134 ++++++++++++++++++++++++++++++++--- packages/views.py | 100 ++------------------------ templates/packages/signoff_cell.html | 4 +- templates/packages/signoffs.html | 12 ++-- 5 files changed, 144 insertions(+), 113 deletions(-) (limited to 'templates/packages') diff --git a/packages/models.py b/packages/models.py index ad082501..3c319fe7 100644 --- a/packages/models.py +++ b/packages/models.py @@ -115,8 +115,11 @@ def full_version(self): return u'%s-%s' % (self.pkgver, self.pkgrel) def __unicode__(self): - return u'%s-%s: %s' % ( - self.pkgbase, self.full_version, self.user) + revoked = u'' + if self.revoked: + revoked = u' (revoked)' + return u'%s-%s: %s%s' % ( + self.pkgbase, self.full_version, self.user, revoked) class PackageGroup(models.Model): ''' diff --git a/packages/utils.py b/packages/utils.py index c8c1f8a6..42cfbe0f 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -1,12 +1,11 @@ -from collections import defaultdict from operator import itemgetter from django.db import connection from django.db.models import Count, Max -from main.models import Package -from main.utils import cache_function -from .models import PackageGroup, PackageRelation, Signoff +from main.models import Package, Repo +from main.utils import cache_function, groupby_preserve_order, PackageStandin +from .models import PackageGroup, PackageRelation, SignoffSpecification, Signoff @cache_function(300) def get_group_info(include_arches=None): @@ -148,8 +147,90 @@ def get_wrong_permissions(): id__in=to_fetch) return relations -def get_current_signoffs(): - '''Returns a mapping of pkgbase -> signoff objects.''' + +DEFAULT_SIGNOFF_SPEC = SignoffSpecification() + +def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_SPEC): + if signoffs: + good_signoffs = sum(1 for s in signoffs if not s.revoked) + return good_signoffs >= spec.required + return False + +class PackageSignoffGroup(object): + '''Encompasses all packages in testing with the same pkgbase.''' + def __init__(self, packages, user=None): + if len(packages) == 0: + raise Exception + self.packages = packages + self.user = user + self.target_repo = None + self.signoffs = set() + self.specification = DEFAULT_SIGNOFF_SPEC + + first = packages[0] + self.pkgbase = first.pkgbase + self.arch = first.arch + self.repo = first.repo + self.version = '' + self.last_update = first.last_update + self.packager = first.packager + + version = first.full_version + if all(version == pkg.full_version for pkg in packages): + self.version = version + + @property + def package(self): + '''Try and return a relevant single package object representing this + group. Start by seeing if there is only one package, then look for the + matching package by name, finally falling back to a standin package + object.''' + if len(self.packages) == 1: + return self.packages[0] + + same_pkgs = [p for p in self.packages if p.pkgname == p.pkgbase] + if same_pkgs: + return same_pkgs[0] + + return PackageStandin(self.packages[0]) + + def find_signoffs(self, all_signoffs): + '''Look through a list of Signoff objects for ones matching this + particular group and store them on the object.''' + for s in all_signoffs: + if s.pkgbase != self.pkgbase: + continue + if self.version and not s.full_version == self.version: + continue + if s.arch_id == self.arch.id and s.repo_id == self.repo.id: + self.signoffs.add(s) + + def approved(self): + return approved_by_signoffs(self.signoffs, self.specification) + + @property + def completed(self): + return sum(1 for s in self.signoffs if not s.revoked) + + @property + def required(self): + return self.specification.required + + def user_signed_off(self, user=None): + '''Did a given user signoff on this package? user can be passed as an + argument, or attached to the group object itself so this can be called + from a template.''' + if user is None: + user = self.user + return user in (s.user for s in self.signoffs if not s.revoked) + + def __unicode__(self): + return u'%s-%s (%s): %d' % ( + self.pkgbase, self.version, self.arch, len(self.signoffs)) + +def get_current_signoffs(repos): + '''Returns a mapping of pkgbase -> signoff objects for the given repos.''' + cursor = connection.cursor() sql = """ SELECT DISTINCT s.id FROM packages_signoff s @@ -162,14 +243,49 @@ def get_current_signoffs(): AND s.repo_id = p.repo_id ) JOIN repos r ON p.repo_id = r.id - WHERE r.testing = %s + WHERE r.id IN ( """ - cursor = connection.cursor() - cursor.execute(sql, [True]) + sql += ", ".join("%s" for r in repos) + sql += ")" + cursor.execute(sql, [r.id for r in repos]) + results = cursor.fetchall() # fetch all of the returned signoffs by ID to_fetch = [row[0] for row in results] signoffs = Signoff.objects.select_related('user').in_bulk(to_fetch) return signoffs.values() +def get_target_repo_map(pkgbases): + package_repos = Package.objects.order_by().values_list( + 'pkgbase', 'repo__name').filter( + repo__testing=False, repo__staging=False, + pkgbase__in=pkgbases).distinct() + return dict(package_repos) + +def get_signoff_groups(repos=None): + if repos is None: + repos = Repo.objects.filter(testing=True) + + test_pkgs = Package.objects.normal().filter(repo__in=repos) + packages = test_pkgs.order_by('pkgname') + + # Collect all pkgbase values in testing repos + q_pkgbase = test_pkgs.values('pkgbase') + pkgtorepo = get_target_repo_map(q_pkgbase) + + # Collect all existing signoffs for these packages + signoffs = get_current_signoffs(repos) + + same_pkgbase_key = lambda x: (x.repo.name, x.arch.name, x.pkgbase) + grouped = groupby_preserve_order(packages, same_pkgbase_key) + signoff_groups = [] + for group in grouped: + signoff_group = PackageSignoffGroup(group) + signoff_group.target_repo = pkgtorepo.get(signoff_group.pkgbase, + "Unknown") + signoff_group.find_signoffs(signoffs) + signoff_groups.append(signoff_group) + + return signoff_groups + # vim: set ts=4 sw=4 et: diff --git a/packages/views.py b/packages/views.py index 035d51cb..e102760b 100644 --- a/packages/views.py +++ b/packages/views.py @@ -23,11 +23,11 @@ from urllib import urlencode from main.models import Package, PackageFile, Arch, Repo -from main.utils import make_choice, groupby_preserve_order, PackageStandin +from main.utils import make_choice from mirrors.models import MirrorUrl -from .models import PackageRelation, PackageGroup, SignoffSpecification, Signoff +from .models import PackageRelation, PackageGroup, Signoff from .utils import (get_group_info, get_differences_info, - get_wrong_permissions, get_current_signoffs) + get_wrong_permissions, get_signoff_groups, approved_by_signoffs) class PackageJSONEncoder(DjangoJSONEncoder): pkg_attributes = [ 'pkgname', 'pkgbase', 'repo', 'arch', 'pkgver', @@ -369,100 +369,12 @@ def unflag_all(request, name, repo, arch): pkgs.update(flag_date=None) return redirect(pkg) -DEFAULT_SIGNOFF_SPEC = SignoffSpecification(required=2) - -def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_SPEC): - if signoffs: - good_signoffs = sum(1 for s in signoffs if not s.revoked) - return good_signoffs >= spec.required - return False - -class PackageSignoffGroup(object): - '''Encompasses all packages in testing with the same pkgbase.''' - def __init__(self, packages, user=None): - if len(packages) == 0: - raise Exception - self.packages = packages - self.user = user - self.target_repo = None - self.signoffs = set() - self.specification = DEFAULT_SIGNOFF_SPEC - - first = packages[0] - self.pkgbase = first.pkgbase - self.arch = first.arch - self.repo = first.repo - self.version = '' - - version = first.full_version - if all(version == pkg.full_version for pkg in packages): - self.version = version - - @property - def package(self): - '''Try and return a relevant single package object representing this - group. Start by seeing if there is only one package, then look for the - matching package by name, finally falling back to a standin package - object.''' - if len(self.packages) == 1: - return self.packages[0] - - same_pkgs = [p for p in self.packages if p.pkgname == p.pkgbase] - if same_pkgs: - return same_pkgs[0] - - return PackageStandin(self.packages[0]) - - def find_signoffs(self, all_signoffs): - '''Look through a list of Signoff objects for ones matching this - particular group and store them on the object.''' - for s in all_signoffs: - if s.pkgbase != self.pkgbase: - continue - if self.version and not s.full_version == self.version: - continue - if s.arch_id == self.arch.id and s.repo_id == self.repo.id: - self.signoffs.add(s) - - def approved(self): - return approved_by_signoffs(self.signoffs, self.specification) - - def user_signed_off(self, user=None): - '''Did a given user signoff on this package? user can be passed as an - argument, or attached to the group object itself so this can be called - from a template.''' - if user is None: - user = self.user - return user in (s.user for s in self.signoffs if not s.revoked) - @permission_required('main.change_package') @never_cache def signoffs(request): - test_pkgs = Package.objects.normal().filter(repo__testing=True) - packages = test_pkgs.order_by('pkgname') - - # Collect all pkgbase values in testing repos - q_pkgbase = test_pkgs.values('pkgbase') - package_repos = Package.objects.order_by().values_list( - 'pkgbase', 'repo__name').filter( - repo__testing=False, repo__staging=False, - pkgbase__in=q_pkgbase).distinct() - pkgtorepo = dict(package_repos) - - # Collect all existing signoffs for these packages - signoffs = get_current_signoffs() - - same_pkgbase_key = lambda x: (x.repo.name, x.arch.name, x.pkgbase) - grouped = groupby_preserve_order(packages, same_pkgbase_key) - signoff_groups = [] - for group in grouped: - signoff_group = PackageSignoffGroup(group, user=request.user) - signoff_group.target_repo = pkgtorepo.get(signoff_group.pkgbase, - "Unknown") - signoff_group.find_signoffs(signoffs) - signoff_groups.append(signoff_group) - - signoff_groups.sort(key=attrgetter('pkgbase')) + signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase')) + for group in signoff_groups: + group.user = request.user context = { 'signoff_groups': signoff_groups, diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html index fce5d551..87216193 100644 --- a/templates/packages/signoff_cell.html +++ b/templates/packages/signoff_cell.html @@ -5,8 +5,8 @@ </ul> {% if group.user_signed_off %} <div><a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/revoke/" - title="Revoke signoff {{ group.package.pkgname }} for {{ group.package.arch }}">Revoke Signoff</a></div> + title="Revoke signoff {{ group.pkgbase }} for {{ group.arch }}">Revoke Signoff</a></div> {% else %} <div><a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/" - title="Signoff {{ group.package.pkgname }} for {{ group.package.arch }}">Signoff</a></div> + title="Signoff {{ group.pkgbase }} for {{ group.arch }}">Signoff</a></div> {% endif %} diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 4a2f6c99..8d57a8c5 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -34,6 +34,7 @@ <h3>Filter Displayed Signoffs</h3> <th>Package Base/Version</th> <th>Arch</th> <th>Target Repo</th> + <th>Packager</th> <th># of Packages</th> <th>Last Updated</th> <th>Approved</th> @@ -42,18 +43,17 @@ <h3>Filter Displayed Signoffs</h3> </thead> <tbody id="tbody_signoffs"> {% for group in signoff_groups %} - {% with group.package as pkg %} - <tr class="{% cycle 'odd' 'even' %} {{ pkg.arch.name }}"> - <td>{% pkg_details_link pkg %} {{ pkg.full_version }}</td> - <td>{{ pkg.arch.name }}</td> + <tr class="{% cycle 'odd' 'even' %} {{ group.arch.name }}"> + <td>{% pkg_details_link group.package %} {{ group.version }}</td> + <td>{{ group.arch.name }}</td> <td>{{ group.target_repo }}</td> + <td>{{ group.packager|default:"Unknown" }}</td> <td>{{ group.packages|length }}</td> - <td>{{ pkg.last_update|date }}</td> + <td>{{ group.last_update|date }}</td> <td class="signoff-{{ group.approved|yesno }}"> {{ group.approved|yesno|capfirst }}</td> <td>{% include "packages/signoff_cell.html" %}</td> </tr> - {% endwith %} {% endfor %} </tbody> </table> -- cgit v1.2.3-54-g00ecf From 49ac7efd683152e4936f8013bb7a001470260034 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 3 Nov 2011 17:18:55 -0500 Subject: Package signoff email report, initial revision Signed-off-by: Dan McGee <dan@archlinux.org> --- packages/management/__init__.py | 0 packages/management/commands/__init__.py | 0 packages/management/commands/signoff_report.py | 110 +++++++++++++++++++++++++ templates/packages/signoff_report.txt | 27 ++++++ 4 files changed, 137 insertions(+) create mode 100644 packages/management/__init__.py create mode 100644 packages/management/commands/__init__.py create mode 100644 packages/management/commands/signoff_report.py create mode 100644 templates/packages/signoff_report.txt (limited to 'templates/packages') diff --git a/packages/management/__init__.py b/packages/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/management/commands/__init__.py b/packages/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/management/commands/signoff_report.py b/packages/management/commands/signoff_report.py new file mode 100644 index 00000000..17e58f39 --- /dev/null +++ b/packages/management/commands/signoff_report.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +""" +signoff_report command + +Send an email summarizing the state of outstanding signoffs for the given +repository. + +Usage: ./manage.py signoff_report <email> <repository> +""" + +from django.core.urlresolvers import reverse +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.db.models import Count +from django.template import loader, Context + +from collections import namedtuple +from datetime import datetime, timedelta +import logging +from operator import attrgetter +import sys + +from main.models import Package, Repo +from packages.models import Signoff +from packages.utils import get_signoff_groups + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s -> %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + stream=sys.stderr) +logger = logging.getLogger() + +class Command(BaseCommand): + args = "<email> <repository>" + help = "Send a signoff report for the given repository." + + def handle(self, *args, **options): + v = int(options.get('verbosity', None)) + if v == 0: + logger.level = logging.ERROR + elif v == 1: + logger.level = logging.INFO + elif v == 2: + logger.level = logging.DEBUG + + if len(args) != 2: + raise CommandError("email and repository must be provided") + + return generate_report(args[0], args[1]) + +def generate_report(email, repo_name): + repo = Repo.objects.get(name__iexact=repo_name) + # Collect all existing signoffs for these packages + signoff_groups = sorted(get_signoff_groups([repo]), + key=attrgetter('target_repo', 'arch', 'pkgbase')) + complete = [] + incomplete = [] + new = [] + old = [] + + new_hours = 24 + old_days = 14 + now = datetime.utcnow() + new_cutoff = now - timedelta(hours=new_hours) + old_cutoff = now - timedelta(days=old_days) + + for group in signoff_groups: + if group.approved(): + complete.append(group) + else: + incomplete.append(group) + if group.package.last_update > new_cutoff: + new.append(group) + if group.package.last_update < old_cutoff: + old.append(group) + + old.sort(key=attrgetter('last_update')) + + proto = 'https' + domain = Site.objects.get_current().domain + signoffs_url = '%s://%s%s' % (proto, domain, reverse('package-signoffs')) + + # and the fun bit + Leader = namedtuple('Leader', ['user', 'count']) + leaders = Signoff.objects.filter(created__gt=new_cutoff, + revoked__isnull=True).values_list('user').annotate( + signoff_count=Count('pk')).order_by('-signoff_count')[:5] + users = User.objects.in_bulk([l[0] for l in leaders]) + leaders = (Leader(users[l[0]], l[1]) for l in leaders) + + subject = 'Signoff report for [%s]' % repo.name.lower() + t = loader.get_template('packages/signoff_report.txt') + c = Context({ + 'repo': repo, + 'signoffs_url': signoffs_url, + 'incomplete': incomplete, + 'complete': complete, + 'new': new, + 'new_hours': new_hours, + 'old': old, + 'old_days': old_days, + 'leaders': leaders, + }) + from_addr = 'Arch Website Notification <nobody@archlinux.org>' + #send_mail(subject, t.render(c), from_addr, email) + print t.render(c) + +# vim: set ts=4 sw=4 et: diff --git a/templates/packages/signoff_report.txt b/templates/packages/signoff_report.txt new file mode 100644 index 00000000..84e3fc6b --- /dev/null +++ b/templates/packages/signoff_report.txt @@ -0,0 +1,27 @@ +=== {% autoescape off %}Signoff report for [{{ repo|lower }}] === +{{ signoffs_url }} + +== New packages in [{{ repo|lower}}] in last {{ new_hours }} hours ({{ new|length }} total) == +{% for group in new %} +* {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}){% endfor %} + +{% regroup incomplete by target_repo as by_repo %}{% for target_repo in by_repo %} +== Incomplete signoffs for [{{ target_repo.grouper|lower }}] ({{ target_repo.list|length }} total) == +{% for group in target_repo.list %} +* {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}) + {{ group.completed }}/{{ group.required }} signoffs{% endfor %} +{% endfor %} + +== Completed signoffs ({{ complete|length }} total) == +{% for group in complete %} +* {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}){% endfor %} + + +== All packages in [{{ repo|lower }}] for more than {{ old_days }} days ({{ old|length }} total) == +{% for group in old %} +* {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}), since {{ group.last_update|date }}{% endfor %} +{% endautoescape %} + +== Top five in signoffs in last {{ new_hours }} hours == +{% for leader in leaders %} +{{ forloop.counter }}. {{ leader.user }} - {{ leader.count }} signoffs{% endfor %} -- cgit v1.2.3-54-g00ecf From caa15d61a3882d1076378ae406bb2effcb63ff87 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 3 Nov 2011 17:29:05 -0500 Subject: Minor tweaks to style and sorting of signoffs Signed-off-by: Dan McGee <dan@archlinux.org> --- media/archweb.css | 4 ++++ templates/packages/signoffs.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'templates/packages') diff --git a/media/archweb.css b/media/archweb.css index 62dc4fbc..f817e18d 100644 --- a/media/archweb.css +++ b/media/archweb.css @@ -895,6 +895,10 @@ ul.admin-actions { } /* dev: signoff page */ +#dev-signoffs tr:hover { + background: #ffd; +} + #dev-signoffs ul { list-style: none; margin: 0; diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 8d57a8c5..0bdc6d46 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -65,7 +65,7 @@ <h3>Filter Displayed Signoffs</h3> $(document).ready(function() { $('a.signoff-link').click(signoff_package); $(".results").tablesorter({widgets: ['zebra'], sortList: [[0,0]], - headers: { 6: { sorter: false } } }); + headers: { 7: { sorter: false } } }); $('#signoffs_filter input').change(filter_signoffs); $('#criteria_reset').click(filter_signoffs_reset); // fire function on page load to ensure the current form selections take effect -- cgit v1.2.3-54-g00ecf From 8187b87143081a2be75032db91287f9deb9d1f89 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 3 Nov 2011 19:10:07 -0500 Subject: Add signoff options form and data entry page This allows the criteria and other information about certain signoffs to be overridden as necessary. Signed-off-by: Dan McGee <dan@archlinux.org> --- packages/models.py | 52 ++++++++++++++++++++++++++------- packages/urls.py | 1 + packages/views.py | 40 ++++++++++++++++++++++++- templates/packages/signoff_cell.html | 15 ++++++++-- templates/packages/signoff_options.html | 18 ++++++++++++ templates/packages/signoffs.html | 3 +- 6 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 templates/packages/signoff_options.html (limited to 'templates/packages') diff --git a/packages/models.py b/packages/models.py index 3c319fe7..a2b53a06 100644 --- a/packages/models.py +++ b/packages/models.py @@ -38,6 +38,22 @@ def __unicode__(self): class Meta: unique_together = (('pkgbase', 'user', 'type'),) + +class SignoffSpecificationManager(models.Manager): + def get_from_package(self, pkg): + '''Utility method to pull all relevant name-version fields from a + package and get a matching specification.''' + return self.get( + pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, + epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo) + + def get_or_create_from_package(self, pkg): + '''Utility method to pull all relevant name-version fields from a + package and get or create a matching specification.''' + return self.get_or_create( + pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, + epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo) + class SignoffSpecification(models.Model): ''' A specification for the signoff policy for this particular revision of a @@ -53,25 +69,40 @@ class SignoffSpecification(models.Model): repo = models.ForeignKey('main.Repo') user = models.ForeignKey(User) created = models.DateTimeField(editable=False) - required = models.PositiveIntegerField(default=2) - enabled = models.BooleanField(default=True) - known_bad = models.BooleanField(default=False) + required = models.PositiveIntegerField(default=2, + help_text="How many signoffs are required for this package?") + enabled = models.BooleanField(default=True, + help_text="Is this package eligible for signoffs?") + known_bad = models.BooleanField(default=False, + help_text="Is package is known to be broken in some way?") comments = models.TextField(null=True, blank=True) + objects = SignoffSpecificationManager() + + @property + def full_version(self): + if self.epoch > 0: + return u'%d:%s-%s' % (self.epoch, self.pkgver, self.pkgrel) + return u'%s-%s' % (self.pkgver, self.pkgrel) + + def __unicode__(self): + return u'%s-%s' % (self.pkgbase, self.full_version) + + class SignoffManager(models.Manager): def get_from_package(self, pkg, user, revoked=False): '''Utility method to pull all relevant name-version fields from a - package and create a matching signoff.''' + package and get a matching signoff.''' not_revoked = not revoked - return Signoff.objects.get( + return self.get( pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo, revoked__isnull=not_revoked, user=user) def get_or_create_from_package(self, pkg, user): '''Utility method to pull all relevant name-version fields from a - package and create a matching signoff.''' - return Signoff.objects.get_or_create( + package and get or create a matching signoff.''' + return self.get_or_create( pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo, revoked=None, user=user) @@ -196,9 +227,8 @@ def remove_inactive_maintainers(sender, instance, created, **kwargs): post_save.connect(remove_inactive_maintainers, sender=User, dispatch_uid="packages.models") -pre_save.connect(set_created_field, sender=PackageRelation, - dispatch_uid="packages.models") -pre_save.connect(set_created_field, sender=Signoff, - dispatch_uid="packages.models") +for sender in (PackageRelation, SignoffSpecification, Signoff): + pre_save.connect(set_created_field, sender=sender, + dispatch_uid="packages.models") # vim: set ts=4 sw=4 et: diff --git a/packages/urls.py b/packages/urls.py index 576e3279..4d391a3c 100644 --- a/packages/urls.py +++ b/packages/urls.py @@ -11,6 +11,7 @@ (r'^unflag/all/$', 'unflag_all'), (r'^signoff/$', 'signoff_package'), (r'^signoff/revoke/$', 'signoff_package', {'revoke': True}), + (r'^signoff/options/$', 'signoff_options'), (r'^download/$', 'download'), ) diff --git a/packages/views.py b/packages/views.py index e102760b..66bcd3fc 100644 --- a/packages/views.py +++ b/packages/views.py @@ -25,7 +25,7 @@ from main.models import Package, PackageFile, Arch, Repo from main.utils import make_choice from mirrors.models import MirrorUrl -from .models import PackageRelation, PackageGroup, Signoff +from .models import PackageRelation, PackageGroup, SignoffSpecification, Signoff from .utils import (get_group_info, get_differences_info, get_wrong_permissions, get_signoff_groups, approved_by_signoffs) @@ -417,6 +417,44 @@ def signoff_package(request, name, repo, arch, revoke=False): return redirect('package-signoffs') +class SignoffOptionsForm(forms.ModelForm): + class Meta: + model = SignoffSpecification + fields = ('required', 'enabled', 'known_bad', 'comments') + +@permission_required('main.change_package') +@never_cache +def signoff_options(request, name, repo, arch): + packages = get_list_or_404(Package, pkgbase=name, + arch__name=arch, repo__name__iexact=repo, repo__testing=True) + package = packages[0] + + # TODO ensure submitter is maintainer and/or packager + + try: + spec = SignoffSpecification.objects.get_from_package(package) + except SignoffSpecification.DoesNotExist: + # create a fake one, but don't save it just yet + spec = SignoffSpecification(pkgbase=package.pkgbase, + pkgver=package.pkgver, pkgrel=package.pkgrel, + epoch=package.epoch, arch=package.arch, repo=package.repo) + spec.user = request.user + + if request.POST: + form = SignoffOptionsForm(request.POST, instance=spec) + if form.is_valid(): + form.save() + return redirect('package-signoffs') + else: + form = SignoffOptionsForm(instance=spec) + + context = { + 'packages': packages, + 'package': package, + 'form': form, + } + return direct_to_template(request, 'packages/signoff_options.html', context) + def flaghelp(request): return direct_to_template(request, 'packages/flaghelp.html') diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html index 87216193..0a630119 100644 --- a/templates/packages/signoff_cell.html +++ b/templates/packages/signoff_cell.html @@ -1,12 +1,23 @@ +{% spaceless %} +{% if group.signoffs %} <ul> {% for signoff in group.signoffs %} <li class="signed-username" title="Signed off by {{ signoff.user }}">{{ signoff.user }}{% if signoff.revoked %} (revoked){% endif %}</li> {% endfor %} </ul> +{% endif %} {% if group.user_signed_off %} -<div><a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/revoke/" +<div> + <a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/revoke/" title="Revoke signoff {{ group.pkgbase }} for {{ group.arch }}">Revoke Signoff</a></div> {% else %} -<div><a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/" +<div> + <a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/" title="Signoff {{ group.pkgbase }} for {{ group.arch }}">Signoff</a></div> {% endif %} +{% if group.packager == user %} +<div> + <a class="signoff-options" href="{{ group.package.get_absolute_url }}signoff/options/">Packager Options</a> +</div> +{% endif %} +{% endspaceless %} diff --git a/templates/packages/signoff_options.html b/templates/packages/signoff_options.html new file mode 100644 index 00000000..ee9b8b47 --- /dev/null +++ b/templates/packages/signoff_options.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Arch Linux - Package Signoff Options - {{ package.pkgbase }} {{ package.full_version }} ({{ package.arch.name }}){% endblock %} +{% block head %}<meta name="robots" content="noindex"/>{% endblock %} +{% block navbarclass %}anb-packages{% endblock %} + +{% block content %} +<div id="signoff-options" class="box"> + <h2>Package Signoff Options: {{ package.pkgbase }} {{ package.full_version }} ({{ package.arch.name }})</h2> + <form id="signoff-options-form" method="post">{% csrf_token %} + <fieldset> + {{ form.as_p }} + </fieldset> + <p><label></label> <input title="Set Signoff Options" type="submit" value="Set Signoff Options" /></p> + </form> + +</div> +{% endblock %} diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 0bdc6d46..9bc7fd74 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -50,8 +50,7 @@ <h3>Filter Displayed Signoffs</h3> <td>{{ group.packager|default:"Unknown" }}</td> <td>{{ group.packages|length }}</td> <td>{{ group.last_update|date }}</td> - <td class="signoff-{{ group.approved|yesno }}"> - {{ group.approved|yesno|capfirst }}</td> + <td class="signoff-{{ group.approved|yesno }}">{{ group.approved|yesno|capfirst }}</td> <td>{% include "packages/signoff_cell.html" %}</td> </tr> {% endfor %} -- cgit v1.2.3-54-g00ecf From 5f2c3bf98baabf919681525e600639643aa2c119 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 3 Nov 2011 20:39:59 -0500 Subject: Signoffs changes and improvements * Better signoff report with more detail * Show signoff specification in signoffs view * Honor disabled/bad flags and display in approval column * Various other small bugfixes and tweaks Signed-off-by: Dan McGee <dan@archlinux.org> --- media/archweb.css | 8 +++++-- media/archweb.js | 30 ++++++++++++++++---------- packages/management/commands/signoff_report.py | 13 ++++++++++- packages/models.py | 28 +++++++++++++++++------- packages/utils.py | 16 ++++++++------ packages/views.py | 15 +++++++++---- templates/packages/signoff_cell.html | 2 ++ templates/packages/signoff_report.txt | 13 +++++++++++ templates/packages/signoffs.html | 19 ++++++++++++++-- 9 files changed, 109 insertions(+), 35 deletions(-) (limited to 'templates/packages') diff --git a/media/archweb.css b/media/archweb.css index f817e18d..303173f2 100644 --- a/media/archweb.css +++ b/media/archweb.css @@ -914,8 +914,12 @@ ul.admin-actions { color: red; } -#dev-signoffs .signed-username { - color: #888; +#dev-signoffs .signoff-bad { + color: darkorange; +} + +#dev-signoffs .signoff-disabled { + color: gray; } /* iso testing feedback form */ diff --git a/media/archweb.js b/media/archweb.js index 43812b33..a9f4e0c9 100644 --- a/media/archweb.js +++ b/media/archweb.js @@ -215,28 +215,33 @@ function signoff_package() { $.getJSON(link.href, function(data) { link = $(link); var signoff = null; + var cell = link.closest('td'); if (data.created) { signoff = $('<li>').addClass('signed-username').text(data.user); - link.closest('td').children('ul').append(signoff); + var list = cell.children('ul'); + if (list.size() == 0) { + list = $('<ul>').prependTo(cell); + } + list.append(signoff); } else if(data.user) { signoff = link.closest('td').find('li').filter(function(index) { return $(this).text() == data.user; }); } - console.log(signoff, data.revoked, data.user); if (signoff && data.revoked) { signoff.text(signoff.text() + ' (revoked)'); } /* update the approved column to reflect reality */ - var approved; - if (data.approved) { - approved = link.closest('tr').children('.signoff-no'); - approved.text('Yes').addClass( - 'signoff-yes').removeClass('signoff-no'); + var approved = link.closest('tr').children('.approval'); + approved.attr('class', ''); + if (data.known_bad) { + approved.text('Bad').addClass('signoff-bad'); + } else if (!data.enabled) { + approved.text('Disabled').addClass('signoff-disabled'); + } else if (data.approved) { + approved.text('Yes').addClass('signoff-yes'); } else { - approved = link.closest('tr').children('.signoff-yes'); - approved.text('No').addClass( - 'signoff-no').removeClass('signoff-yes'); + approved.text('No').addClass('signoff-no'); } link.removeAttr('title'); /* Form our new link. The current will be something like @@ -245,6 +250,10 @@ function signoff_package() { if (data.revoked) { link.text('Signoff'); link.attr('href', base_href + '/signoff/'); + /* should we be hiding the link? */ + if (data.known_bad || !data.enabled) { + link.remove(); + } } else { link.text('Revoke Signoff'); link.attr('href', base_href + '/signoff/revoke/'); @@ -260,7 +269,6 @@ function filter_signoffs() { var all_rows = rows; $('#signoffs_filter .arch_filter').each(function() { if (!$(this).is(':checked')) { - console.log($(this).val()); rows = rows.not('.' + $(this).val()); } }); diff --git a/packages/management/commands/signoff_report.py b/packages/management/commands/signoff_report.py index 02f3d985..3431dada 100644 --- a/packages/management/commands/signoff_report.py +++ b/packages/management/commands/signoff_report.py @@ -56,6 +56,8 @@ def generate_report(email, repo_name): # Collect all existing signoffs for these packages signoff_groups = sorted(get_signoff_groups([repo]), key=attrgetter('target_repo', 'arch', 'pkgbase')) + disabled = [] + bad = [] complete = [] incomplete = [] new = [] @@ -68,10 +70,16 @@ def generate_report(email, repo_name): old_cutoff = now - timedelta(days=old_days) for group in signoff_groups: - if group.approved(): + spec = group.specification + if spec.known_bad: + bad.append(group) + elif not spec.enabled: + disabled.append(group) + elif group.approved(): complete.append(group) else: incomplete.append(group) + if group.package.last_update > new_cutoff: new.append(group) if group.package.last_update < old_cutoff: @@ -96,6 +104,9 @@ def generate_report(email, repo_name): c = Context({ 'repo': repo, 'signoffs_url': signoffs_url, + 'disabled': disabled, + 'bad': bad, + 'all': signoff_groups, 'incomplete': incomplete, 'complete': complete, 'new': new, diff --git a/packages/models.py b/packages/models.py index a2b53a06..b70c21bf 100644 --- a/packages/models.py +++ b/packages/models.py @@ -1,3 +1,5 @@ +from collections import namedtuple + from django.db import models from django.db.models.signals import pre_save, post_save from django.contrib.auth.models import User @@ -42,22 +44,26 @@ class Meta: class SignoffSpecificationManager(models.Manager): def get_from_package(self, pkg): '''Utility method to pull all relevant name-version fields from a - package and get a matching specification.''' + package and get a matching signoff specification.''' return self.get( pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo) - def get_or_create_from_package(self, pkg): - '''Utility method to pull all relevant name-version fields from a - package and get or create a matching specification.''' - return self.get_or_create( - pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, - epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo) + def get_or_default_from_package(self, pkg): + '''utility method to pull all relevant name-version fields from a + package and get a matching signoff specification, or return the default + base case.''' + try: + return self.get( + pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, + epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo) + except SignoffSpecification.DoesNotExist: + return DEFAULT_SIGNOFF_SPEC class SignoffSpecification(models.Model): ''' A specification for the signoff policy for this particular revision of a - pakcage. The default is requiring two signoffs for a given package. These + package. The default is requiring two signoffs for a given package. These are created only if necessary; e.g., if one wanted to override the required=2 attribute, otherwise a sane default object is used. ''' @@ -89,6 +95,12 @@ def __unicode__(self): return u'%s-%s' % (self.pkgbase, self.full_version) +# fake default signoff spec when we don't have a persisted one in the database +FakeSignoffSpecification = namedtuple('FakeSignoffSpecification', + ('required', 'enabled', 'known_bad', 'comments')) +DEFAULT_SIGNOFF_SPEC = FakeSignoffSpecification(2, True, False, u'') + + class SignoffManager(models.Manager): def get_from_package(self, pkg, user, revoked=False): '''Utility method to pull all relevant name-version fields from a diff --git a/packages/utils.py b/packages/utils.py index 42cfbe0f..60b95e21 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -5,7 +5,8 @@ from main.models import Package, Repo from main.utils import cache_function, groupby_preserve_order, PackageStandin -from .models import PackageGroup, PackageRelation, SignoffSpecification, Signoff +from .models import (PackageGroup, PackageRelation, + SignoffSpecification, Signoff, DEFAULT_SIGNOFF_SPEC) @cache_function(300) def get_group_info(include_arches=None): @@ -148,9 +149,7 @@ def get_wrong_permissions(): return relations -DEFAULT_SIGNOFF_SPEC = SignoffSpecification() - -def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_SPEC): +def approved_by_signoffs(signoffs, spec): if signoffs: good_signoffs = sum(1 for s in signoffs if not s.revoked) return good_signoffs >= spec.required @@ -158,14 +157,13 @@ def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_SPEC): class PackageSignoffGroup(object): '''Encompasses all packages in testing with the same pkgbase.''' - def __init__(self, packages, user=None): + def __init__(self, packages): if len(packages) == 0: raise Exception self.packages = packages - self.user = user + self.user = None self.target_repo = None self.signoffs = set() - self.specification = DEFAULT_SIGNOFF_SPEC first = packages[0] self.pkgbase = first.pkgbase @@ -175,6 +173,10 @@ def __init__(self, packages, user=None): self.last_update = first.last_update self.packager = first.packager + self.specification = \ + SignoffSpecification.objects.get_or_default_from_package(first) + self.default_spec = self.specification is DEFAULT_SIGNOFF_SPEC + version = first.full_version if all(version == pkg.full_version for pkg in packages): self.version = version diff --git a/packages/views.py b/packages/views.py index 66bcd3fc..307691e2 100644 --- a/packages/views.py +++ b/packages/views.py @@ -7,8 +7,9 @@ from django.core.mail import send_mail from django.core.serializers.json import DjangoJSONEncoder from django.db.models import Q -from django.http import HttpResponse, Http404 -from django.shortcuts import get_object_or_404, get_list_or_404, redirect +from django.http import HttpResponse, Http404, HttpResponseForbidden +from django.shortcuts import (get_object_or_404, get_list_or_404, + redirect, render) from django.template import loader, Context from django.utils import simplejson from django.views.decorators.cache import never_cache @@ -404,12 +405,16 @@ def signoff_package(request, name, repo, arch, revoke=False): package, request.user) all_signoffs = Signoff.objects.for_package(package) + spec = SignoffSpecification.objects.get_or_default_from_package(package) if request.is_ajax(): data = { 'created': created, 'revoked': bool(signoff.revoked), - 'approved': approved_by_signoffs(all_signoffs), + 'approved': approved_by_signoffs(all_signoffs, spec), + 'required': spec.required, + 'enabled': spec.enabled, + 'known_bad': spec.known_bad, 'user': str(request.user), } return HttpResponse(simplejson.dumps(data, ensure_ascii=False), @@ -429,7 +434,9 @@ def signoff_options(request, name, repo, arch): arch__name=arch, repo__name__iexact=repo, repo__testing=True) package = packages[0] - # TODO ensure submitter is maintainer and/or packager + if request.user != package.packager and \ + request.user not in package.maintainers: + return render(request, '403.html', status=403) try: spec = SignoffSpecification.objects.get_from_package(package) diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html index 0a630119..6c705b4e 100644 --- a/templates/packages/signoff_cell.html +++ b/templates/packages/signoff_cell.html @@ -11,10 +11,12 @@ <a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/revoke/" title="Revoke signoff {{ group.pkgbase }} for {{ group.arch }}">Revoke Signoff</a></div> {% else %} +{% if not group.specification.known_bad and group.specification.enabled %} <div> <a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/" title="Signoff {{ group.pkgbase }} for {{ group.arch }}">Signoff</a></div> {% endif %} +{% endif %} {% if group.packager == user %} <div> <a class="signoff-options" href="{{ group.package.get_absolute_url }}signoff/options/">Packager Options</a> diff --git a/templates/packages/signoff_report.txt b/templates/packages/signoff_report.txt index 84e3fc6b..81020c8f 100644 --- a/templates/packages/signoff_report.txt +++ b/templates/packages/signoff_report.txt @@ -1,6 +1,19 @@ === {% autoescape off %}Signoff report for [{{ repo|lower }}] === {{ signoffs_url }} +There are currently: +* {{ new|length }} new package{{ new|length|pluralize }} in last {{ new_hours }} hours +* {{ bad|length }} known bad package{{ bad|length|pluralize }} +* {{ disabled|length }} package{{ disabled|length|pluralize }} not accepting signoffs +* {{ complete|length }} fully signed off package{{ complete|length|pluralize }} +* {{ incomplete|length }} package{{ incomplete|length|pluralize }} missing signoffs +* {{ old|length }} package{{ old|length|pluralize }} older than {{ old_days }} days + +(Note: the word 'package' as used here refers to packages as grouped by +pkgbase, architecture, and repository; e.g., one PKGBUILD produces one +package per architecture, even if it is a split package.) + + == New packages in [{{ repo|lower}}] in last {{ new_hours }} hours ({{ new|length }} total) == {% for group in new %} * {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}){% endfor %} diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 9bc7fd74..d517e5e3 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -39,6 +39,7 @@ <h3>Filter Displayed Signoffs</h3> <th>Last Updated</th> <th>Approved</th> <th>Signoffs</th> + <th>Notes</th> </tr> </thead> <tbody id="tbody_signoffs"> @@ -50,8 +51,22 @@ <h3>Filter Displayed Signoffs</h3> <td>{{ group.packager|default:"Unknown" }}</td> <td>{{ group.packages|length }}</td> <td>{{ group.last_update|date }}</td> - <td class="signoff-{{ group.approved|yesno }}">{{ group.approved|yesno|capfirst }}</td> + {% if group.specification.known_bad %} + <td class="approval signoff-bad">Bad</td> + {% else %} + {% if not group.specification.enabled %} + <td class="approval signoff-disabled">Disabled</td> + {% else %} + <td class="approval signoff-{{ group.approved|yesno }}">{{ group.approved|yesno|capfirst }}</td> + {% endif %} + {% endif %} <td>{% include "packages/signoff_cell.html" %}</td> + <td class="wrap">{% if not group.default_spec %}{% with group.specification as spec %} + {% if spec.required != 2 %}Required signoffs: {{ spec.required }}<br/>{% endif %} + {% if not spec.enabled %}Signoffs are not currently enabled<br/>{% endif %} + {% if spec.known_bad %}Package is known to be bad<br/>{% endif %} + {{ spec.comments|default:""|linebreaks }} + {% endwith %}{% endif %}</td> </tr> {% endfor %} </tbody> @@ -64,7 +79,7 @@ <h3>Filter Displayed Signoffs</h3> $(document).ready(function() { $('a.signoff-link').click(signoff_package); $(".results").tablesorter({widgets: ['zebra'], sortList: [[0,0]], - headers: { 7: { sorter: false } } }); + headers: { 7: { sorter: false }, 8: {sorter: false } } }); $('#signoffs_filter input').change(filter_signoffs); $('#criteria_reset').click(filter_signoffs_reset); // fire function on page load to ensure the current form selections take effect -- cgit v1.2.3-54-g00ecf From 5e295a3dbb0b64f229e9419384721b154e013b9e Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 3 Nov 2011 21:20:50 -0500 Subject: Allow signoff options to apply to all packages across architectures If you check the new box, you can set the options for both the i686 and the x86_64 packages at the same time. Signed-off-by: Dan McGee <dan@archlinux.org> --- packages/views.py | 32 +++++++++++++++++++++++++++++++- templates/packages/signoff_cell.html | 2 +- 2 files changed, 32 insertions(+), 2 deletions(-) (limited to 'templates/packages') diff --git a/packages/views.py b/packages/views.py index 00dd7f7d..aa15d0cf 100644 --- a/packages/views.py +++ b/packages/views.py @@ -6,6 +6,7 @@ from django.conf import settings from django.core.mail import send_mail from django.core.serializers.json import DjangoJSONEncoder +from django.db import transaction from django.db.models import Q from django.http import HttpResponse, Http404, HttpResponseForbidden from django.shortcuts import (get_object_or_404, get_list_or_404, @@ -426,10 +427,36 @@ def signoff_package(request, name, repo, arch, revoke=False): return redirect('package-signoffs') class SignoffOptionsForm(forms.ModelForm): + apply_all = forms.BooleanField(required=False, + help_text="Apply these options to all architectures?") + class Meta: model = SignoffSpecification fields = ('required', 'enabled', 'known_bad', 'comments') +def _signoff_options_all(request, name, repo): + seen_ids = set() + with transaction.commit_on_success(): + # find or create a specification for all architectures, then + # graft the form data onto them + packages = Package.objects.filter(pkgbase=name, + repo__name__iexact=repo, repo__testing=True) + for package in packages: + try: + spec = SignoffSpecification.objects.get_from_package(package) + if spec.pk in seen_ids: + continue + except SignoffSpecification.DoesNotExist: + spec = SignoffSpecification(pkgbase=package.pkgbase, + pkgver=package.pkgver, pkgrel=package.pkgrel, + epoch=package.epoch, arch=package.arch, + repo=package.repo) + spec.user = request.user + form = SignoffOptionsForm(request.POST, instance=spec) + if form.is_valid(): + form.save() + seen_ids.add(form.instance.pk) + @permission_required('main.change_package') @never_cache def signoff_options(request, name, repo, arch): @@ -453,7 +480,10 @@ def signoff_options(request, name, repo, arch): if request.POST: form = SignoffOptionsForm(request.POST, instance=spec) if form.is_valid(): - form.save() + if form.cleaned_data['apply_all']: + _signoff_options_all(request, name, repo) + else: + form.save() return redirect('package-signoffs') else: form = SignoffOptionsForm(instance=spec) diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html index 6c705b4e..4f9f726b 100644 --- a/templates/packages/signoff_cell.html +++ b/templates/packages/signoff_cell.html @@ -19,7 +19,7 @@ {% endif %} {% if group.packager == user %} <div> - <a class="signoff-options" href="{{ group.package.get_absolute_url }}signoff/options/">Packager Options</a> + <a class="signoff-options" href="{{ group.package.get_absolute_url }}signoff/options/">Signoff Options</a> </div> {% endif %} {% endspaceless %} -- cgit v1.2.3-54-g00ecf From 0aa42e2c01df2bf1c9e425994420f5ae10252597 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 3 Nov 2011 21:32:30 -0500 Subject: Allow signoff manipulation if you are a maintainer This is a more expensive and not-yet-optimized way of doing this, but we can fix that later as needed. Signed-off-by: Dan McGee <dan@archlinux.org> --- packages/utils.py | 4 ++++ templates/packages/signoff_cell.html | 2 +- templates/todolists/view.html | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'templates/packages') diff --git a/packages/utils.py b/packages/utils.py index 60b95e21..1a2c0de0 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -2,6 +2,7 @@ from django.db import connection from django.db.models import Count, Max +from django.contrib.auth.models import User from main.models import Package, Repo from main.utils import cache_function, groupby_preserve_order, PackageStandin @@ -172,6 +173,9 @@ def __init__(self, packages): self.version = '' self.last_update = first.last_update self.packager = first.packager + self.maintainers = User.objects.filter( + package_relations__type=PackageRelation.MAINTAINER, + package_relations__pkgbase=self.pkgbase) self.specification = \ SignoffSpecification.objects.get_or_default_from_package(first) diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html index 4f9f726b..0bf44ca2 100644 --- a/templates/packages/signoff_cell.html +++ b/templates/packages/signoff_cell.html @@ -17,7 +17,7 @@ title="Signoff {{ group.pkgbase }} for {{ group.arch }}">Signoff</a></div> {% endif %} {% endif %} -{% if group.packager == user %} +{% if user == group.packager or user in group.maintainers %} <div> <a class="signoff-options" href="{{ group.package.get_absolute_url }}signoff/options/">Signoff Options</a> </div> diff --git a/templates/todolists/view.html b/templates/todolists/view.html index 8f515c9b..c9ea919a 100644 --- a/templates/todolists/view.html +++ b/templates/todolists/view.html @@ -29,7 +29,7 @@ <h2>Todo List: {{ list.name }}</h2> <th>Name</th> <th>Arch</th> <th>Repo</th> - <th>Maintainer</th> + <th>Maintainers</th> <th>Status</th> </tr> </thead> -- cgit v1.2.3-54-g00ecf From 8ba68aed370c2369bebaaca4d4158b6c40223c0f Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Fri, 4 Nov 2011 10:59:45 -0500 Subject: Add filter by target repo on signoffs page And add a count of displayed rows below the filter options. Signed-off-by: Dan McGee <dan@archlinux.org> --- media/archweb.js | 7 ++++++- packages/views.py | 1 + templates/packages/signoffs.html | 12 +++++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) (limited to 'templates/packages') diff --git a/media/archweb.js b/media/archweb.js index a9f4e0c9..2b8e5d6d 100644 --- a/media/archweb.js +++ b/media/archweb.js @@ -267,22 +267,27 @@ function filter_signoffs() { /* start with all rows, and then remove ones we shouldn't show */ var rows = $('#tbody_signoffs').children(); var all_rows = rows; - $('#signoffs_filter .arch_filter').each(function() { + /* apply arch and repo filters */ + $('#signoffs_filter .arch_filter').add( + '#signoffs_filter .repo_filter').each(function() { if (!$(this).is(':checked')) { rows = rows.not('.' + $(this).val()); } }); + /* and then the slightly more expensive pending check */ if ($('#id_pending').is(':checked')) { rows = rows.has('td.signoff-no'); } /* hide all rows, then show the set we care about */ all_rows.hide(); rows.show(); + $('#filter-count').text(rows.length); /* make sure we update the odd/even styling from sorting */ $('.results').trigger('applyWidgets'); } function filter_signoffs_reset() { $('#signoffs_filter .arch_filter').attr('checked', 'checked'); + $('#signoffs_filter .repo_filter').attr('checked', 'checked'); $('#id_pending').removeAttr('checked'); filter_signoffs(); } diff --git a/packages/views.py b/packages/views.py index aa15d0cf..3c0c2bee 100644 --- a/packages/views.py +++ b/packages/views.py @@ -381,6 +381,7 @@ def signoffs(request): context = { 'signoff_groups': signoff_groups, 'arches': Arch.objects.all(), + 'repo_names': sorted(set(g.target_repo for g in signoff_groups)), } return direct_to_template(request, 'packages/signoffs.html', context) diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index d517e5e3..f4511f75 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -9,7 +9,7 @@ <h2>Package Signoffs</h2> - <p>{{ signoff_groups|length }} signoff group{{ signoff_groups|pluralize }} found. + <p>{{ signoff_groups|length }} total signoff group{{ signoff_groups|pluralize }} found. A "signoff group" consists of packages grouped by pkgbase, architecture, and repository.</p> <div class="box filter-criteria"> @@ -21,9 +21,15 @@ <h3>Filter Displayed Signoffs</h3> <div><label for="id_arch_{{ arch.name }}" title="Architecture {{ arch.name }}">Arch {{ arch.name }}</label> <input type="checkbox" name="arch_{{ arch.name }}" id="id_arch_{{ arch.name }}" class="arch_filter" value="{{ arch.name }}" checked="checked"/></div> {% endfor %} + {% for repo_name in repo_names %} + <div><label for="id_repo_{{ repo_name|lower }}" title="Target Repository {{ repo_name }}">[{{ repo_name|lower }}]</label> + <input type="checkbox" name="repo_{{ repo_name|lower }}" id="id_repo_{{ repo_name|lower }}" class="repo_filter" value="{{ repo_name|lower }}" checked="checked"/></div> + {% endfor %} <div><label for="id_pending" title="Packages with not enough signoffs">Only Pending Approval</label> <input type="checkbox" name="pending" id="id_pending" value="pending"/></div> - <div ><label> </label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div> + <div><label> </label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div> + <div class="clear"></div> + <div id="filter-info"><span id="filter-count">{{ signoff_groups|length }}</span> signoff groups displayed.</div> </fieldset> </form> </div> @@ -44,7 +50,7 @@ <h3>Filter Displayed Signoffs</h3> </thead> <tbody id="tbody_signoffs"> {% for group in signoff_groups %} - <tr class="{% cycle 'odd' 'even' %} {{ group.arch.name }}"> + <tr class="{% cycle 'odd' 'even' %} {{ group.arch.name }} {{ group.target_repo|lower }}"> <td>{% pkg_details_link group.package %} {{ group.version }}</td> <td>{{ group.arch.name }}</td> <td>{{ group.target_repo }}</td> -- cgit v1.2.3-54-g00ecf From e565fde00f56c7a01ff55a204a0a56d3ce4bf8b4 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Fri, 4 Nov 2011 11:31:35 -0500 Subject: Signoff email: prune empty content Don't send the email at all if there are no packages even in the repository, and don't print empty sections. Signed-off-by: Dan McGee <dan@archlinux.org> --- packages/management/commands/signoff_report.py | 4 ++++ templates/packages/signoff_report.txt | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'templates/packages') diff --git a/packages/management/commands/signoff_report.py b/packages/management/commands/signoff_report.py index 3431dada..3357bc1e 100644 --- a/packages/management/commands/signoff_report.py +++ b/packages/management/commands/signoff_report.py @@ -69,6 +69,10 @@ def generate_report(email, repo_name): new_cutoff = now - timedelta(hours=new_hours) old_cutoff = now - timedelta(days=old_days) + if len(signoff_groups) == 0: + # no need to send an email at all + return + for group in signoff_groups: spec = group.specification if spec.known_bad: diff --git a/templates/packages/signoff_report.txt b/templates/packages/signoff_report.txt index 81020c8f..046c2f1e 100644 --- a/templates/packages/signoff_report.txt +++ b/templates/packages/signoff_report.txt @@ -14,27 +14,28 @@ pkgbase, architecture, and repository; e.g., one PKGBUILD produces one package per architecture, even if it is a split package.) -== New packages in [{{ repo|lower}}] in last {{ new_hours }} hours ({{ new|length }} total) == +{% if new %}== New packages in [{{ repo|lower}}] in last {{ new_hours }} hours ({{ new|length }} total) == {% for group in new %} * {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}){% endfor %} -{% regroup incomplete by target_repo as by_repo %}{% for target_repo in by_repo %} +{% endif %}{% regroup incomplete by target_repo as by_repo %}{% for target_repo in by_repo %} == Incomplete signoffs for [{{ target_repo.grouper|lower }}] ({{ target_repo.list|length }} total) == {% for group in target_repo.list %} * {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}) {{ group.completed }}/{{ group.required }} signoffs{% endfor %} {% endfor %} -== Completed signoffs ({{ complete|length }} total) == +{% if complete %}== Completed signoffs ({{ complete|length }} total) == {% for group in complete %} * {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}){% endfor %} -== All packages in [{{ repo|lower }}] for more than {{ old_days }} days ({{ old|length }} total) == +{% endif %}{% if old %}== All packages in [{{ repo|lower }}] for more than {{ old_days }} days ({{ old|length }} total) == {% for group in old %} * {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}), since {{ group.last_update|date }}{% endfor %} -{% endautoescape %} -== Top five in signoffs in last {{ new_hours }} hours == + +{% endif %}== Top five in signoffs in last {{ new_hours }} hours == {% for leader in leaders %} {{ forloop.counter }}. {{ leader.user }} - {{ leader.count }} signoffs{% endfor %} +{% endautoescape %} -- cgit v1.2.3-54-g00ecf From e15654fd7cbd5bf5e9a5c7d59b6b2a50999ee467 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Fri, 4 Nov 2011 11:56:38 -0500 Subject: Fix misnamed JS function call Signed-off-by: Dan McGee <dan@archlinux.org> --- templates/packages/differences.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'templates/packages') diff --git a/templates/packages/differences.html b/templates/packages/differences.html index 0400ea37..6c06ae25 100644 --- a/templates/packages/differences.html +++ b/templates/packages/differences.html @@ -65,7 +65,7 @@ <h3>Filter Differences View</h3> $('.results').tablesorter({widgets: ['zebra'], sortList: [[1,0], [0,0]]}); $('#diff_filter select').change(filter_packages); $('#diff_filter input').change(filter_packages); - $('#criteria_reset').click(filter_differences_reset); + $('#criteria_reset').click(filter_packages_reset); // fire function on page load to ensure the current form selections take effect filter_packages(); }); -- cgit v1.2.3-54-g00ecf From 21d5f818a60ab2626f941f8ff53e263e802494d5 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Fri, 11 Nov 2011 10:42:51 -0600 Subject: Touch up signoff page styles Signed-off-by: Dan McGee <dan@archlinux.org> --- media/archweb.css | 10 +++++----- templates/packages/signoff_cell.html | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'templates/packages') diff --git a/media/archweb.css b/media/archweb.css index 303173f2..c5477422 100644 --- a/media/archweb.css +++ b/media/archweb.css @@ -899,26 +899,26 @@ ul.admin-actions { background: #ffd; } -#dev-signoffs ul { +ul.signoff-list { list-style: none; margin: 0; padding: 0; } -#dev-signoffs .signoff-yes { +.signoff-yes { color: green; font-weight: bold; } -#dev-signoffs .signoff-no { +.signoff-no { color: red; } -#dev-signoffs .signoff-bad { +.signoff-bad { color: darkorange; } -#dev-signoffs .signoff-disabled { +.signoff-disabled { color: gray; } diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html index 0bf44ca2..01a5d58d 100644 --- a/templates/packages/signoff_cell.html +++ b/templates/packages/signoff_cell.html @@ -1,6 +1,6 @@ {% spaceless %} {% if group.signoffs %} -<ul> +<ul class="signoff-list"> {% for signoff in group.signoffs %} <li class="signed-username" title="Signed off by {{ signoff.user }}">{{ signoff.user }}{% if signoff.revoked %} (revoked){% endif %}</li> {% endfor %} -- cgit v1.2.3-54-g00ecf From 12408702eaf89ea338670ba808da9ef49e35c562 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Mon, 14 Nov 2011 12:19:17 -0600 Subject: Allow population of signoff specs with SVN commit messages This pulls them from the latest SVN commit on trunk. We don't have a failproof method of getting the exact right commit, but this should be close if it is run on a regular basis via cron (aka hourly). Note that running locally, I needed the development version of South to get the migration included here to apply because of information_schema changes in the current version of MySQL. Signed-off-by: Dan McGee <dan@archlinux.org> --- packages/management/commands/populate_signoffs.py | 89 +++++++++++ packages/management/commands/signoff_report.py | 2 +- ...11_auto__chg_field_signoffspecification_user.py | 165 +++++++++++++++++++++ packages/models.py | 2 +- packages/views/signoff.py | 5 + settings.py | 4 + templates/packages/signoffs.html | 2 +- 7 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 packages/management/commands/populate_signoffs.py create mode 100644 packages/migrations/0011_auto__chg_field_signoffspecification_user.py (limited to 'templates/packages') diff --git a/packages/management/commands/populate_signoffs.py b/packages/management/commands/populate_signoffs.py new file mode 100644 index 00000000..5b5acbaf --- /dev/null +++ b/packages/management/commands/populate_signoffs.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" +populate_signoffs command + +Pull the latest commit message from SVN for a given package that is +signoff-eligible and does not have an existing comment attached. + +Usage: ./manage.py populate_signoffs +""" + +from datetime import datetime +import logging +import subprocess +import sys +from xml.etree.ElementTree import XML + +from django.conf import settings +from django.contrib.auth.models import User +from django.core.management.base import NoArgsCommand + +from ...models import SignoffSpecification +from ...utils import get_signoff_groups + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s -> %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + stream=sys.stderr) +logger = logging.getLogger() + +class Command(NoArgsCommand): + help = "Pull the latest commit message from SVN for a given package that is signoff-eligible and does not have an existing comment attached" + + def handle_noargs(self, **options): + v = int(options.get('verbosity', None)) + if v == 0: + logger.level = logging.ERROR + elif v == 1: + logger.level = logging.INFO + elif v == 2: + logger.level = logging.DEBUG + + return add_signoff_comments() + +def svn_log(pkgbase, repo): + path = '%s%s/%s/trunk/' % (settings.SVN_BASE_URL, repo.svn_root, pkgbase) + cmd = ['svn', 'log', '--limit=1', '--xml', path] + log_data = subprocess.check_output(cmd) + # the XML format is very very simple, especially with only one revision + xml = XML(log_data) + revision = int(xml.find('logentry').get('revision')) + date = datetime.strptime(xml.findtext('logentry/date'), + '%Y-%m-%dT%H:%M:%S.%fZ') + return { + 'revision': revision, + 'date': date, + 'author': xml.findtext('logentry/author'), + 'message': xml.findtext('logentry/msg'), + } + +def create_specification(package, log): + trimmed_message = log['message'].strip() + spec = SignoffSpecification(pkgbase=package.pkgbase, + pkgver=package.pkgver, pkgrel=package.pkgrel, + epoch=package.epoch, arch=package.arch, repo=package.repo, + comments=trimmed_message) + try: + spec.user = User.objects.get(username=log['author']) + except User.DoesNotExist: + pass + + return spec + +def add_signoff_comments(): + logger.info("getting all signoff groups") + groups = get_signoff_groups() + logger.info("%d signoff groups found", len(groups)) + + for group in groups: + if not group.default_spec: + continue + + logger.debug("getting SVN log for %s (%s)", group.pkgbase, group.repo) + log = svn_log(group.pkgbase, group.repo) + logger.info("creating spec with SVN message for %s", group.pkgbase) + spec = create_specification(group.packages[0], log) + spec.save() + +# vim: set ts=4 sw=4 et: diff --git a/packages/management/commands/signoff_report.py b/packages/management/commands/signoff_report.py index 3357bc1e..3b67f518 100644 --- a/packages/management/commands/signoff_report.py +++ b/packages/management/commands/signoff_report.py @@ -22,7 +22,7 @@ from operator import attrgetter import sys -from main.models import Package, Repo +from main.models import Repo from packages.models import Signoff from packages.utils import get_signoff_groups diff --git a/packages/migrations/0011_auto__chg_field_signoffspecification_user.py b/packages/migrations/0011_auto__chg_field_signoffspecification_user.py new file mode 100644 index 00000000..f6e3cdd9 --- /dev/null +++ b/packages/migrations/0011_auto__chg_field_signoffspecification_user.py @@ -0,0 +1,165 @@ +# encoding: utf-8 +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.alter_column('packages_signoffspecification', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)) + + def backwards(self, orm): + db.alter_column('packages_signoffspecification', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['auth.User'])) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "['name']", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "['name']", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'packages.conflict': { + 'Meta': {'ordering': "['name']", 'object_name': 'Conflict'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conflicts'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.license': { + 'Meta': {'ordering': "['name']", 'object_name': 'License'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'licenses'", 'to': "orm['main.Package']"}) + }, + 'packages.packagegroup': { + 'Meta': {'object_name': 'PackageGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Package']"}) + }, + 'packages.packagerelation': { + 'Meta': {'unique_together': "(('pkgbase', 'user', 'type'),)", 'object_name': 'PackageRelation'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_relations'", 'to': "orm['auth.User']"}) + }, + 'packages.provision': { + 'Meta': {'ordering': "['name']", 'object_name': 'Provision'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provides'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.replacement': { + 'Meta': {'ordering': "['name']", 'object_name': 'Replacement'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'replaces'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.signoff': { + 'Meta': {'object_name': 'Signoff'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'revoked': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_signoffs'", 'to': "orm['auth.User']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index b70c21bf..0d02ab31 100644 --- a/packages/models.py +++ b/packages/models.py @@ -73,7 +73,7 @@ class SignoffSpecification(models.Model): epoch = models.PositiveIntegerField(default=0) arch = models.ForeignKey('main.Arch') repo = models.ForeignKey('main.Repo') - user = models.ForeignKey(User) + user = models.ForeignKey(User, null=True) created = models.DateTimeField(editable=False) required = models.PositiveIntegerField(default=2, help_text="How many signoffs are required for this package?") diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 26b6e710..e57b4d9a 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -98,7 +98,10 @@ def _signoff_options_all(request, name, repo): pkgver=package.pkgver, pkgrel=package.pkgrel, epoch=package.epoch, arch=package.arch, repo=package.repo) + + if spec.user is None: spec.user = request.user + form = SignoffOptionsForm(request.POST, instance=spec) if form.is_valid(): form.save() @@ -122,6 +125,8 @@ def signoff_options(request, name, repo, arch): spec = SignoffSpecification(pkgbase=package.pkgbase, pkgver=package.pkgver, pkgrel=package.pkgrel, epoch=package.epoch, arch=package.arch, repo=package.repo) + + if spec.user is None: spec.user = request.user if request.POST: diff --git a/settings.py b/settings.py index 51f9fcf6..80e024af 100644 --- a/settings.py +++ b/settings.py @@ -134,4 +134,8 @@ # URL to fetch a current list of available ISOs ISO_LIST_URL = 'http://releng.archlinux.org/isos/' +# URL for SVN access for fetching commit messages (note absence of packages or +# community bit on the end, repo.svn_root is appended) +SVN_BASE_URL = 'svn+ssh://svn.archlinux.org/srv/svn-' + # vim: set ts=4 sw=4 et: diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index f4511f75..bd84289c 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -71,7 +71,7 @@ <h3>Filter Displayed Signoffs</h3> {% if spec.required != 2 %}Required signoffs: {{ spec.required }}<br/>{% endif %} {% if not spec.enabled %}Signoffs are not currently enabled<br/>{% endif %} {% if spec.known_bad %}Package is known to be bad<br/>{% endif %} - {{ spec.comments|default:""|linebreaks }} + {{ spec.comments|default:""|linebreaksbr }} {% endwith %}{% endif %}</td> </tr> {% endfor %} -- cgit v1.2.3-54-g00ecf From f43a33ed8696d7bcb987d4878c6411c5d16846d6 Mon Sep 17 00:00:00 2001 From: Dan McGee <dan@archlinux.org> Date: Thu, 17 Nov 2011 13:32:42 -0600 Subject: Display package URLs unquoted if possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example: kbd-ru-keymaps. Before: http://wiki.archlinux.org/index.php/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F After: http://wiki.archlinux.org/index.php/Интернационализация Signed-off-by: Dan McGee <dan@archlinux.org> --- packages/templatetags/package_extras.py | 13 ++++++++++++- templates/packages/details.html | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'templates/packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index 01bf7510..67c7fbbc 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -1,4 +1,4 @@ -from urllib import urlencode, quote as urlquote +from urllib import urlencode, quote as urlquote, unquote try: from urlparse import parse_qs except ImportError: @@ -13,6 +13,17 @@ def link_encode(url, query, doseq=False): data = urlencode(query, doseq).replace('&', '&') return "%s?%s" % (url, data) +@register.filter +def url_unquote(original_url): + try: + url = original_url + if isinstance(url, unicode): + url = url.encode('ascii') + url = unquote(url).decode('utf-8') + return url + except UnicodeError: + return original_url + class BuildQueryStringNode(template.Node): def __init__(self, sortfield): self.sortfield = sortfield diff --git a/templates/packages/details.html b/templates/packages/details.html index 2998592f..a9908012 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -108,7 +108,7 @@ <h4>Versions Elsewhere</h4> </tr><tr> <th>Upstream URL:</th> <td>{% if pkg.url %}<a href="{{ pkg.url }}" - title="Visit the website for {{ pkg.pkgname }}">{{ pkg.url }}</a>{% endif %}</td> + title="Visit the website for {{ pkg.pkgname }}">{{ pkg.url|url_unquote }}</a>{% endif %}</td> </tr><tr> <th>License(s):</th> <td>{{ pkg.licenses.all|join:", " }}</td> -- cgit v1.2.3-54-g00ecf