Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Major update - first working version of Release-based model

  • Loading branch information...
commit be7faf53f29f9680534e3402a581f025b573cf42 1 parent e6695c3
@mihneasim authored
View
4 media/css/base.css
@@ -16,6 +16,10 @@ body{
color: #999999;
}
+a img{
+ border: none;
+}
+
/* toggle boxes in table */
tr.toggle_info{
display: none;
View
20 media/css/tipsy.css
@@ -0,0 +1,20 @@
+.tipsy { padding: 5px; font-size: 12px; line-height: 15px; position: absolute; z-index: 100000; font-style: italic;}
+ .tipsy-inner { padding: 10px 13px 9px 13px; background-color: #fff; color: #444; max-width: 200px; text-align: left;/*
+-moz-box-shadow: 0px 0px 5px #00aeef;
+-webkit-box-shadow: 0px 0px 5px #00aeef;
+box-shadow: 0px 0px 5px #00aeef;
+*/
+ border: 2px solid #000;
+ }
+ .tipsy-inner { border-radius: 3px; -moz-border-radius:3px; -webkit-border-radius:3px; }
+ .tipsy-arrow { position: absolute; background: url('../img/tipsy.gif') no-repeat top left; width: 9px; height: 5px; }
+ .tipsy-n .tipsy-arrow { top: 0; left: 50%; margin-left: -4px; }
+ .tipsy-nw .tipsy-arrow { top: 0; left: 10px; }
+ .tipsy-ne .tipsy-arrow { top: 0; right: 10px; }
+ .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -4px; background-position: bottom left; }
+ .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; background-position: bottom left; }
+ .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; background-position: bottom left; }
+ .tipsy-e .tipsy-arrow { top: 50%; margin-top: -4px; right: 0; width: 5px; height: 9px; background-position: top right; }
+ .tipsy-w .tipsy-arrow { top: 50%; margin-top: -4px; left: 0; width: 5px; height: 9px; }
+.tipsy ul {padding: 5px 0 0 15px; color: #444; list-style-type: disc;}
+.tipsy ul li {color: #000; font-style: normal; font-size: 11px;}
View
BIN  media/img/down-arrow.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  media/img/tipsy.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
17 media/js/common.js
@@ -0,0 +1,17 @@
+var tips_register = function (manual){
+ if (manual==undefined)
+ manual = false;
+ var trigger = manual ? 'manual' : 'hover';
+ var gravity = 'e';
+ for(i=0; i<tipdict.length; i++){
+ if(!tipdict[i].gravity)
+ gravity = 'e';
+ else
+ gravity = tipdict[i].gravity;
+ jQuery(tipdict[i].id).tipsy({ trigger: trigger,
+ fade: true, gravity: gravity, html: true,
+ title: tipdict[i].title});
+ }
+ };
+
+jQuery(document).ready(function(){ tips_register(); });
View
202 media/js/jquery.tipsy.js
@@ -0,0 +1,202 @@
+// tipsy, facebook style tooltips for jquery
+// version 1.0.0a
+// (c) 2008-2010 jason frame [jason@onehackoranother.com]
+// released under the MIT license
+
+(function($) {
+
+ function Tipsy(element, options) {
+ this.$element = $(element);
+ this.options = options;
+ this.enabled = true;
+ this.fixTitle();
+ }
+
+ Tipsy.prototype = {
+ show: function() {
+ var title = this.getTitle();
+ if (title && this.enabled) {
+ var $tip = this.tip();
+
+ $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
+ $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
+ $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
+
+ var pos = $.extend({}, this.$element.offset(), {
+ width: this.$element[0].offsetWidth,
+ height: this.$element[0].offsetHeight
+ });
+
+ var actualWidth = $tip[0].offsetWidth, actualHeight = $tip[0].offsetHeight;
+ var gravity = (typeof this.options.gravity == 'function')
+ ? this.options.gravity.call(this.$element[0])
+ : this.options.gravity;
+
+ var tp;
+ switch (gravity.charAt(0)) {
+ case 'n':
+ tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
+ break;
+ case 's':
+ tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
+ break;
+ case 'e':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
+ break;
+ case 'w':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
+ break;
+ }
+
+ if (gravity.length == 2) {
+ if (gravity.charAt(1) == 'w') {
+ tp.left = pos.left + pos.width / 2 - 15;
+ } else {
+ tp.left = pos.left + pos.width / 2 - actualWidth + 15;
+ }
+ }
+
+ $tip.css(tp).addClass('tipsy-' + gravity);
+
+ if (this.options.fade) {
+ $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
+ } else {
+ $tip.css({visibility: 'visible', opacity: this.options.opacity});
+ }
+ }
+ },
+
+ hide: function() {
+ if (this.options.fade) {
+ this.tip().stop().fadeOut(function() { $(this).remove(); });
+ } else {
+ this.tip().remove();
+ }
+ },
+
+ fixTitle: function() {
+ var $e = this.$element;
+ if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
+ $e.attr('original-title', $e.attr('title') || '').removeAttr('title');
+ }
+ },
+
+ getTitle: function() {
+ var title, $e = this.$element, o = this.options;
+ this.fixTitle();
+ var title, o = this.options;
+ if (typeof o.title == 'string') {
+ title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
+ } else if (typeof o.title == 'function') {
+ title = o.title.call($e[0]);
+ }
+ title = ('' + title).replace(/(^\s*|\s*$)/, "");
+ return title || o.fallback;
+ },
+
+ tip: function() {
+ if (!this.$tip) {
+ this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>');
+ }
+ return this.$tip;
+ },
+
+ validate: function() {
+ if (!this.$element[0].parentNode) {
+ this.hide();
+ this.$element = null;
+ this.options = null;
+ }
+ },
+
+ enable: function() { this.enabled = true; },
+ disable: function() { this.enabled = false; },
+ toggleEnabled: function() { this.enabled = !this.enabled; }
+ };
+
+ $.fn.tipsy = function(options) {
+
+ if (options === true) {
+ return this.data('tipsy');
+ } else if (typeof options == 'string') {
+ var tipsy = this.data('tipsy');
+ if (tipsy) tipsy[options]();
+ return this;
+ }
+
+ options = $.extend({}, $.fn.tipsy.defaults, options);
+
+ function get(ele) {
+ var tipsy = $.data(ele, 'tipsy');
+ if (!tipsy) {
+ tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
+ $.data(ele, 'tipsy', tipsy);
+ }
+ return tipsy;
+ }
+
+ function enter() {
+ var tipsy = get(this);
+ tipsy.hoverState = 'in';
+ if (options.delayIn == 0) {
+ tipsy.show();
+ } else {
+ tipsy.fixTitle();
+ setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
+ }
+ };
+
+ function leave() {
+ var tipsy = get(this);
+ tipsy.hoverState = 'out';
+ if (options.delayOut == 0) {
+ tipsy.hide();
+ } else {
+ setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
+ }
+ };
+
+ if (!options.live) this.each(function() { get(this); });
+
+ if (options.trigger != 'manual') {
+ var binder = options.live ? 'live' : 'bind',
+ eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
+ eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
+ this[binder](eventIn, enter)[binder](eventOut, leave);
+ }
+
+ return this;
+
+ };
+
+ $.fn.tipsy.defaults = {
+ delayIn: 0,
+ delayOut: 0,
+ fade: false,
+ fallback: '',
+ gravity: 'n',
+ html: false,
+ live: false,
+ offset: 0,
+ opacity: 1,
+ title: 'title',
+ trigger: 'hover'
+ };
+
+ // Overwrite this method to provide options on a per-element basis.
+ // For example, you could store the gravity in a 'tipsy-gravity' attribute:
+ // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
+ // (remember - do not modify 'options' in place!)
+ $.fn.tipsy.elementOptions = function(ele, options) {
+ return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
+ };
+
+ $.fn.tipsy.autoNS = function() {
+ return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
+ };
+
+ $.fn.tipsy.autoWE = function() {
+ return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
+ };
+
+})(jQuery);
View
15 media/js/tipdict.js
@@ -0,0 +1,15 @@
+var tipdict =
+[
+ {id: '.toggle_update_info',
+ title: function(){ return "Click to toggle a short manual of update procedure";},
+ gravity: 'se'
+ },
+ {id: '.toggle_changelog_info',
+ title: function(){ return "Click to toggle package changelog and detailed changes";},
+ gravity: 's'
+ },
+ {id: '.edit_release',
+ title: function(){ return "Click to edit release information";},
+ gravity: 's'
+ }
+];
View
192 nystatus/ChangelogClient.py
@@ -1,5 +1,12 @@
import os.path
import sys
+from datetime import date
+import re
+import logging
+from docutils.utils import new_document
+from docutils.parsers.rst import Parser
+from docutils.frontend import OptionParser
+import subprocess
# Django connection
crt = os.path.dirname(os.path.realpath(__file__))
@@ -10,16 +17,197 @@
import settings
setup_environ(settings)
+from django.db.transaction import commit_on_success
+
+from nystatus.models import Release, Release_update_or_add, Product
+from nystatus.templatetags.pversion import unpversion, pversion
+
+# Pretty rotating logger
+LOG_FILENAME = os.path.join(settings.ABS_ROOT, 'var' + os.path.sep + 'changelog_client.log')
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+handler = logging.handlers.RotatingFileHandler(
+ LOG_FILENAME, maxBytes=1000000, backupCount=5)
+formatter = logging.Formatter('%(levelname)s %(asctime)s: %(message)s', '%d %b %H:%M')
+handler.setFormatter(formatter)
+logger.addHandler(handler)
+
+class EmptyChangelog(Exception):
+ pass
+
+class MalformedChangelog(Exception):
+ pass
+
+class MalformedHeader(MalformedChangelog):
+ pass
class ChangelogClient(object):
def __init__(self, product):
self.product = product
+ self.changelog = None
+ self.blame = {}
+ if self.product.changelog_path:
+ data = re.compile(r' +([0-9]+) +\S+ +(.*)')
+ p = subprocess.Popen('svn blame %s' % self.product.changelog_path,
+ shell=True,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ close_fds=True)
+ blame = p.communicate('\n')[0]
+ chunks = []
+ for (lineno, line) in enumerate(blame.split('\n')):
+ if not line:
+ continue
+ m = data.search(line)
+ revision = m.groups()[0]
+ chunks.append(m.groups()[1])
+ self.blame[lineno] = revision
+
+ self.changelog = '\n'.join(chunks)
+
+ def _get_sortable_version(self, headerline):
+ """
+ Returns '0001.0002.0003' out of '1.2.3 (2011-09-10|unreleased)'
+
+ """
+ if '(' not in headerline:
+ raise MalformedHeader("No '(' char found in headerline")
+ version = headerline[:headerline.find('(')].strip()
+ if version.endswith("-dev"):
+ version = version[:-4]
+ ints = map(int, version.split("."))
+ return ".".join(["%0.4d" % i for i in ints])
+
+ def _get_date(self, headerline):
+ """
+ Returns datetime.date or None if unreleased
+ out of '1.2.3 (2011-09-10|unreleased)'
- def update_changelog(changelog):
+ """
+ pat = re.compile(r'\( *(\d{4}-\d{2}-\d{2}) *\)|\( *(unreleased) *\)',
+ re.I)
+ m = pat.search(headerline)
+ if m is None:
+ raise MalformedHeader("Can not match date or `unreleased`")
+ (udate, ureleased) = m.groups()
+ if ureleased:
+ return None
+ else:
+ r_date = map(int, udate.split("-"))
+ return date(r_date[0], r_date[1], r_date[2])
+
+ def update_changelog(self, changelog):
"""
Updates changelog in db for wrapped product
with given `changelog` multiline string
"""
- pass
+ docsettings = OptionParser(components=(Parser,)).get_default_values()
+ document = new_document(u'Changelog Document Instance', docsettings)
+ parser = Parser()
+ parser.parse(changelog, document)
+ if not len(document.children):
+ raise EmptyChangelog()
+
+ releases = {}
+ for block in document.children:
+ headline = block[0].astext()
+ r_date = self._get_date(headline)
+ version = self._get_sortable_version(headline)
+ text = ''
+ if len(block) == 1:
+ # must be unreleased
+ assert(r_date is None)
+ else:
+ # TODO: figure out a better way to get rst of doctree
+ entries = block[1].astext().split(block.child_text_separator)
+ text = '* ' + '\n* '.join(entries)
+ releases[version] = {'datev': r_date, 'changelog': text}
+
+ found_versions = releases.keys()
+ found_versions.sort()
+ last_released = Release.objects.filter(product=self.product, datev__isnull=False)
+ last_released = last_released.order_by('-datev')
+
+ try:
+ last_released = last_released[0]
+ last_released_version = last_released.version
+ except:
+ last_released_version = ''
+ after_last_released = False
+
+ needs_blame = []
+ for version in found_versions:
+ # walk versions in new changelog, from oldest to newest
+ if after_last_released:
+ needs_blame.append(pversion(version))
+ # this is unreleased in db, probably totally changed in changelog
+ # update 1 on 1, regardless of version
+ (unreleased, c) = Release.objects.get_or_create(product=self.product, datev__isnull=True)
+ unreleased.version = version
+ unreleased.datev = releases[version]['datev']
+ unreleased.changelog = releases[version]['changelog']
+ unreleased.save()
+ after_last_released = False
+ else:
+ # either exists in db, either totally new
+ added = Release_update_or_add(self.product, version,
+ releases[version]['datev'],
+ releases[version]['changelog'])
+ if added:
+ needs_blame.append(pversion(version))
+ if version == last_released_version:
+ after_last_released = True
+
+ self.update_commit_info(needs_blame)
+
+ def update_commit_info(self, versions):
+ """ `versions` is list of versions that need to be rechecked """
+ if not self.changelog:
+ return
+ revisions = {}
+ for v in versions:
+ for (index, line) in enumerate(self.changelog.split('\n')):
+ if line.startswith(v + ' '):
+ revisions[v] = self.blame[index]
+
+ for (version, revision) in revisions.items():
+ release = Release.objects.get(product=self.product,
+ version=unpversion(version))
+ # get commit info from svn
+ p = subprocess.Popen('svn log %s -r%s' % (settings.SVN_PATH, revision),
+ shell=True,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ close_fds=True)
+ output=p.communicate('\n')[0]
+ lines = output.split('\n')
+ firstline = lines[1]
+ chunks = firstline.split("|")
+
+ # update commit info in db
+ release.number = 'r' + str(revision)
+ release.author = chunks[1].strip()
+ release.message = lines[3]
+ release.datec = ' '.join(chunks[2].strip().split(' ')[:2])
+ release.save()
+
+ @commit_on_success
+ def update(self):
+ try:
+ self.update_changelog(self.changelog)
+ except Exception, e:
+ logger.exception("Error in updating product %s" % self.product)
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ products = Product.objects.all().exclude(changelog_path__isnull=True)
+ products = products.exclude(changelog_path='')
+ for product in products:
+ cl = ChangelogClient(product)
+ cl.update()
+ else:
+ product = Product.objects.get(pk=int(sys.argv[1]))
+ cl = ChangelogClient(product)
+ cl.update()
View
14 nystatus/admin.py
@@ -86,14 +86,18 @@ def clean_number(self):
class ReleaseAdmin(admin.ModelAdmin):
form = ValidateCommitNumber
- list_display = ('product', 'version',
- 'number', 'record_type', #'also_affects_set',
- 'obs', 'author', 'message', 'datec',
+ list_display = ('product', 'version', 'datev', 'record_type',
+
+ 'number', #'also_affects_set',
+ 'author', 'message', 'changelog', 'datec',
+
'doc_update', 'requires_update', 'update_info',
+ 'obs',
'date')
search_fields = ('number', 'obs', 'update_info', 'changelog')
- ordering = ('-datec', '-version', '-number', '-date')
- readonly_fields = ('changelog', 'datec', 'author', 'message', 'datev')
+ ordering = ('product', '-version', '-number', '-date')
+ readonly_fields = ('product', 'version', 'datev',
+ 'number', 'datec', 'author', 'message', 'changelog',)
list_filter = ('record_type', 'requires_update', 'product',
'author', 'doc_update')
View
4 nystatus/client.py
@@ -314,10 +314,8 @@ def update_errors(self, instance, portal=None, json_str=''):
client.update_errors(i)
else:
try:
- i = int(sys.argv[1])
-
# force update of particular instance
- instance_id = sys.argv[1]
+ instance_id = int(sys.argv[1])
try:
i = ZopeInstance.objects.get(pk=instance_id)
client.update_info(instance_id)
View
38 nystatus/models.py
@@ -39,6 +39,9 @@ class Product(models.Model):
my_notes = models.TextField('Notes', blank=True)
latest_found_version = models.CharField(max_length=15, blank=True)
use_count = models.PositiveIntegerField(default=0)
+ changelog_path = models.CharField(max_length=255, null=True, blank=True,
+ default=None,
+ help_text='E.g. https://svn.eionet.europa.eu/repositories/Naaya/trunk/eggs/naaya-survey/HISTORY.txt')
def __unicode__(self):
return self.name # + ' [' + str(self.pk) + ']'
@@ -112,7 +115,7 @@ class Release(models.Model):
# Repository related fields:
number = models.CharField('Last Revision', max_length=40, db_index=True,
- unique=True, default='r',
+ default='r',
help_text='Revision number or commit id')
author = models.CharField(max_length=16, db_index=True)
message = models.TextField('Commit Message')
@@ -120,8 +123,9 @@ class Release(models.Model):
# _THIS_ is actual extra changelog
obs = models.TextField('Detailed Information',
- help_text='Use reStructured text format.')
- record_type = models.CharField('Type', max_length=1, default='f',
+ help_text=('Write here any changes with external behavior. '
+ 'Use reStructured text format.'))
+ record_type = models.CharField('Type', max_length=1, default='o',
choices=(('f', 'Feature'), ('b', 'Bug fix'),
('r', 'Refactoring'), ('o', 'Other')
),
@@ -135,7 +139,7 @@ class Release(models.Model):
requires_update = models.BooleanField('Requires Update', default=False,
help_text='Check if update procedure is required',
db_index=True)
- update_info = models.TextField('Updating information', blank=True, null=True,
+ update_info = models.TextField('Update Manual', blank=True, null=True,
help_text=('If `requires update`, provide full info here: '
'scripts, procedures, tracebacks, common issues.'
' Use reStructured text format.'))
@@ -143,22 +147,10 @@ class Release(models.Model):
# Model specific
date = models.DateTimeField('Last updated', auto_now=True)
- def save(self):
- if not self.id:
- # insert action
- # grab commit info from svn
- self.message = ''
- p = subprocess.Popen('svn log %s -%s' % (SVN_PATH, self.number),
- shell=True,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- close_fds=True)
- output=p.communicate('\n')[0]
- lines = output.split('\n')
- firstline = lines[1]
- chunks = firstline.split("|")
- self.author = chunks[1].strip()
- self.datec = ' '.join(chunks[2].strip().split(' ')[:2])
- self.message = '<br />\n'.join(lines[3:-2])
-
- super(Release, self).save()
+
+def Release_update_or_add(product, version, datev, changelog):
+ (rel, c) = Release.objects.get_or_create(product=product, version=version)
+ rel.datev = datev
+ rel.changelog = changelog
+ rel.save()
+ return c
View
91 nystatus/templates/nystatus/index.html
@@ -1,11 +1,20 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01
Transitional//EN"><html>
{% load resthtml %}
+{% load pversion %}
<head>
<title>Extended changelog</title>
<link type='text/css' rel='stylesheet' href='{{ ADMIN_MEDIA_PREFIX }}css/base.css' />
<link type='text/css' rel='stylesheet' href='{{ MEDIA_URL }}css/base.css' />
+
+ <link rel="stylesheet" href="/media/css/tipsy.css" type="text/css" media="screen" />
+
+
<script type='text/javascript' src='{{ MEDIA_URL }}js/jquery.min.js'></script>
+<script type='text/javascript' src='{{ MEDIA_URL }}js/jquery.tipsy.js'></script>
+<script type='text/javascript' src='{{ MEDIA_URL }}js/tipdict.js'></script>
+<script type='text/javascript' src='{{ MEDIA_URL }}js/common.js'></script>
+
<script type='text/javascript' src='{{ MEDIA_URL }}js/advanced_view.js'></script>
</head>
<body>
@@ -14,7 +23,7 @@
<hr />
<div style='text-align: right; font-weight: bold;'>
- <a href='admin/nystatus/commit/'>Proceed to admin &raquo;</a>
+ <a href='admin/nystatus/release/'>Proceed to admin &raquo;</a>
</div>
<div style='text-align: center; margin: 0px auto;'>
@@ -22,6 +31,7 @@
<thead>
<th>Product</th>
<th>Version</th>
+ <th>Release Date</th>
<th>Type</th>
<th style='width: 10em;'>Requires update</th>
<th style='width: 10em;'>Documentation update</th>
@@ -30,56 +40,69 @@
<th>Commit message</th>
</thead>
{% autoescape off %}
- {% for c in commits %}
- <tr class="{% cycle 'row1' 'row2' %} changelog_row" id="recored-{{ c.pk }}">
- <td class="product_name">{{c.product|default_if_none:'-'}}</td>
- <td>{{c.version}}</td>
- <td>{{c.get_record_type_display}}</td>
- <td class="{{c.requires_update|yesno:"high_priority," }}"
- ><img src="{{ ADMIN_MEDIA_PREFIX }}img/admin/icon-{{ c.requires_update|yesno:"yes,no"}}.gif" />
- {% if c.requires_update %}
- &nbsp; <a href="javascript:void(0);" class="toggle_update_info">toggle update info</a>
+ {% for rel in releases %}
+ <tr class="{% cycle 'row1' 'row2' %} changelog_row" id="recored-{{ rel.pk }}">
+ <td class="product_name">{{rel.product|default_if_none:'-'}}</td>
+ <td>
+ {% if request.user.is_staff%}
+ <a href='admin/nystatus/release/{{ rel.pk }}/' class="edit_release">
+ {{rel.version|pversion}}
+ </a>
+ {% else %}
+ {{rel.version|pversion}}
+ {% endif %}
+ </td>
+ <td>{{rel.datev|default_if_none:"unreleased"}}</td>
+ <td>{{rel.get_record_type_display}}</td>
+ <td class="{{rel.requires_update|yesno:"high_priority," }}"
+ ><img src="{{ ADMIN_MEDIA_PREFIX }}img/admin/icon-{{ rel.requires_update|yesno:"yes,no"}}.gif" />
+ {% if rel.requires_update %}
+ &nbsp; <a href="javascript:void(0);" class="toggle_update_info">
+ YES <!--<img src="{{ MEDIA_URL }}img/down-arrow.png" />-->
+ </a>
{% endif %}
</td>
- <td><img src="{{ ADMIN_MEDIA_PREFIX }}img/admin/icon-{{ c.doc_update|yesno:"yes,no"}}.gif" /></td>
+ <td><img src="{{ ADMIN_MEDIA_PREFIX }}img/admin/icon-{{ rel.doc_update|yesno:"yes,no"}}.gif" /></td>
<td>
- <a href="https://svn.eionet.europa.eu/projects/Naaya/changeset/{{c.int_number}}" target="_blank">
- {{c.number}}
+ <a href="https://svn.eionet.europa.eu/projects/Naaya/changeset/{{rel.int_number}}" target="_blank">
+ {{rel.number}}
</a>
by
- {{c.author}}<br />
- <span class="out_of_focus">{{c.datec}}</span>
+ {{rel.author}}<br />
+ <span class="out_of_focus">{{rel.datec}}</span>
</td>
<td>
- {{c.message}}
- {% if c.obs %}
+ {{rel.message}}
&nbsp; <a href="javascript:void(0);" class="toggle_changelog_info">more</a>
- {% endif %}
</td>
</tr>
<!-- {% cycle 'row1' 'row2' %} -->
- {% if c.requires_update %}
- <tr id="update_info-{{c.pk}}" class="toggle_info" >
- <td colspan="7">
+ {% if rel.requires_update %}
+ <tr id="update_info-{{rel.pk}}" class="toggle_info" >
+ <td colspan="8">
<div class="high_priority">
- {{ c.update_info|resthtml }}
+ {{ rel.update_info|resthtml }}
</div>
</td>
</tr>
{% endif %}
- {% if c.obs %}
- <tr id="changelog_info-{{c.pk}}" class="toggle_info {% cycle 'row1' 'row2' %}" >
- <td colspan="7">
- <div>
- <b>Changelog {{c.product|default_if_none:""}} {{c.version}}</b><br />
- {{ c.obs|resthtml }}
- </div>
- </td>
- </tr>
- {% endif %}
- <!-- {{c.obs|resthtml}} -->
+
+ <tr id="changelog_info-{{rel.pk}}" class="toggle_info {% cycle 'row1' 'row2' %}" >
+ <td colspan="8">
+ <div>
+ <b>Developers Changelog {{rel.product|default_if_none:""}} {{rel.version|pversion}}</b><br />
+ {{ rel.changelog|resthtml }}
+ {% if rel.obs %}
+ <b>Public Changelog</b><br />
+ {{ rel.obs|resthtml }}
+ {% endif %}
+ </div>
+ </td>
+ </tr>
+
+ <!-- {{rel.obs|resthtml}} -->
<!--
<tr class="{% cycle 'row1' 'row2' %}">
@@ -91,7 +114,7 @@
<div style='text-align: right; font-weight: bold;
margin-top: 1em;'>
- <a href='admin/nystatus/commit/'>Proceed to admin &raquo;</a>
+ <a href='admin/nystatus/release/'>Proceed to admin &raquo;</a>
</div>
</div>
</div>
View
16 nystatus/templatetags/pversion.py
@@ -0,0 +1,16 @@
+from django import template
+
+def pversion(value):
+ """ Turns 0001.0012.0034 in 1.12.34 """
+ try:
+ return '.'.join(map(str, map(int, value.split('.'))))
+ except Exception, e:
+ return '-'
+
+def unpversion(value):
+ """ Turns 1.12.34 in 0001.0012.0034 """
+ ints = map(int, value.split("."))
+ return ".".join(["%0.4d" % i for i in ints])
+
+register = template.Library()
+register.filter('pversion', pversion)
View
16 nystatus/testdata/changelog0.txt
@@ -0,0 +1,16 @@
+
+1.2.6 (unreleased)
+==================
+ * Bugfix in RadioWidget.get_value
+ * Administrators can now edit answers in expired surveys
+
+1.2.5 (2011-09-23)
+==================
+ * Merge Products.NaayaSurvey and Products.NaayaWidgets into a single package
+ named "naaya-survey"
+ * Cleaned up code and fixed #666
+
+1.2.2 (2011-04-28)
+==================
+ * Last version where Products.NaayaSurvey and Products.NaayaWidgets were
+ separate packages
View
24 nystatus/testdata/changelog1.txt
@@ -0,0 +1,24 @@
+
+1.3.2 (unreleased)
+==================
+
+1.3.1 (2011-10-03)
+==================
+ * Bugfix in `Administrators can now edit..`
+
+1.3.0 (2011-09-30)
+==================
+ * Another bugfix that now looks ok
+ * Bugfix in RadioWidget.get_value
+ * Administrators can now edit answers in expired surveys
+
+1.2.5 (2011-09-23)
+==================
+ * Merge Products.NaayaSurvey and Products.NaayaWidgets into a single package
+ named "naaya-survey"
+ * Cleaned up code and fixed #666
+
+1.2.2 (2011-04-28)
+==================
+ * Last version where Products.NaayaSurvey and Products.NaayaWidgets were
+ separate packages
View
13 nystatus/testdata/json_errors.json
@@ -0,0 +1,13 @@
+[{"date": 1280414307.3505969, "url":
+ "error_log/showEntry?id=1280414307.350.485226514453", "error_name":
+ "http://localhost:8080/destinet/as%21123d", "error_type": "NotFound","id":
+ "1280414307.350.485226514453"}, {"date": 1280414304.021045, "url":
+ "error_log/showEntry?id=1280414304.020.396680920369", "error_name":
+ "http://localhost:8080/destinet/as123d", "error_type": "NotFound", "id":
+ "1280414304.020.396680920369"}, {"date": 1280414301.4547751, "url":
+ "error_log/showEntry?id=1280414301.450.709259786227", "error_name":
+ "http://localhost:8080/destinet/asdsdfsd", "error_type": "NotFound", "id":
+ "1280414301.450.709259786227"}, {"date": 1280414298.7367339, "url":
+ "error_log/showEntry?id=1280414298.740.231762162563", "error_name":
+ "http://localhost:8080/destinet/asd", "error_type": "NotFound", "id":
+ "1280414298.740.231762162563"}]
View
86 nystatus/tests.py
@@ -15,11 +15,23 @@
# Django imports
from nystatus.models import *
+from nystatus.templatetags.pversion import pversion
# My imports
from client import Client
from ChangelogClient import ChangelogClient
+
+def sampledata(filename):
+ f = open(os.path.join(settings.ABS_ROOT,
+ 'nystatus' + os.path.sep + 'testdata' + os.path.sep +
+ filename),
+ 'r')
+ content = f.read()
+ f.close()
+ return content
+
+
class ClientTestCase(unittest.TestCase):
instance = 'xxxTesting Instancexxx'
url = 'http://testing.test'
@@ -28,19 +40,7 @@ class ClientTestCase(unittest.TestCase):
'"Fictional prod 2"}, {"version": "3.0.3-4", "name": "Fictional prod 3"}]')
json_getPortals = ('["fake_portal0", "fake_portal1", "fake_portal2", '
'"fake_portal3"]')
- json_getErrors = ('[{"date": 1280414307.3505969, "url":'
- '"error_log/showEntry?id=1280414307.350.485226514453", "error_name":'
- '"http://localhost:8080/destinet/as%21123d", "error_type": "NotFound","id":'
- '"1280414307.350.485226514453"}, {"date": 1280414304.021045, "url":'
- '"error_log/showEntry?id=1280414304.020.396680920369", "error_name":'
- '"http://localhost:8080/destinet/as123d", "error_type": "NotFound", "id":'
- '"1280414304.020.396680920369"}, {"date": 1280414301.4547751, "url":'
- '"error_log/showEntry?id=1280414301.450.709259786227", "error_name":'
- '"http://localhost:8080/destinet/asdsdfsd", "error_type": "NotFound", "id":'
- '"1280414301.450.709259786227"}, {"date": 1280414298.7367339, "url":'
- '"error_log/showEntry?id=1280414298.740.231762162563", "error_name":'
- '"http://localhost:8080/destinet/asd", "error_type": "NotFound", "id":'
- '"1280414298.740.231762162563"}]')
+ json_getErrors = sampledata('json_errors.json')
pcount = 4
ins = None
@@ -121,55 +121,13 @@ def test_compare_versions(self):
class ChangelogClientTestCase(unittest.TestCase):
- sample = {'changelog0': """
-1.2.6 (unreleased)
-==================
- * Bugfix in RadioWidget.get_value
- * Administrators can now edit answers in expired surveys
-
-1.2.5 (2011-09-23)
-==================
- * Merge Products.NaayaSurvey and Products.NaayaWidgets into a single package
- named "naaya-survey"
- * Cleaned up code and fixed #666
-
-1.2.2 (2011-04-28)
-==================
- * Last version where Products.NaayaSurvey and Products.NaayaWidgets were
- separate packages
-
- """,
-
- 'changelog1': """
-
-1.3.2 (unreleased)
-==================
-
-1.3.1 (2011-10-03)
-==================
- * Bugfix in `Administrators can now edit..`
-
-1.3.0 (2011-09-30)
-==================
- * Another bugfix that now looks ok
- * Bugfix in RadioWidget.get_value
- * Administrators can now edit answers in expired surveys
-
-1.2.5 (2011-09-23)
-==================
- * Merge Products.NaayaSurvey and Products.NaayaWidgets into a single package
- named "naaya-survey"
- * Cleaned up code and fixed #666
-
-1.2.2 (2011-04-28)
-==================
- * Last version where Products.NaayaSurvey and Products.NaayaWidgets were
- separate packages
- """,
+ sample = {'changelog0': sampledata('changelog0.txt'),
+ 'changelog1': sampledata('changelog1.txt'),
'name': 'naaya-survey'}
def setUp(self):
self.product = Product(name=self.sample['name'], origin='n')
+ self.product.save()
self.client = ChangelogClient(self.product)
def test_changelog_parser(self):
@@ -177,13 +135,13 @@ def test_changelog_parser(self):
versions = Release.objects.filter(product=self.product)
indexed = {}
for v in versions:
- indexed[v.version] = v
+ indexed[pversion(v.version)] = v
self.assertEqual(len(versions), 3)
self.assertEqual(set(indexed.keys()), set(['1.2.2', '1.2.5', '1.2.6']))
self.assertEqual(indexed['1.2.6'].datev, None)
self.assertEqual(indexed['1.2.5'].datev, date(2011, 9, 23))
- self.assertEqual(indexed['1.2.2'].changelog, """ * Last version where Products.NaayaSurvey and Products.NaayaWidgets were
- separate packages""")
+ self.assertEqual(indexed['1.2.2'].changelog, """* Last version where Products.NaayaSurvey and Products.NaayaWidgets were
+separate packages""")
def test_changelog_incremental_update(self):
self.client.update_changelog(self.sample['changelog0'])
@@ -191,11 +149,13 @@ def test_changelog_incremental_update(self):
unreleased = Release.objects.get(product=self.product, datev=None)
unreleased.obs = 'My personal observations aka extended changelog'
unreleased.save()
+
self.client.update_changelog(self.sample['changelog1'])
+ versions = Release.objects.filter(product=self.product)
indexed = {}
for v in versions:
- indexed[v.version] = v
- self.assertEqual(set(indexed.keys),
+ indexed[pversion(v.version)] = v
+ self.assertEqual(set(indexed.keys()),
set(['1.2.2', '1.2.5', '1.3.0', '1.3.1', '1.3.2']))
self.assertEqual(indexed['1.3.0'].obs,
'My personal observations aka extended changelog')
View
6 nystatus/views.py
@@ -18,10 +18,10 @@ def admin_trigger(self, id):
def index(request):
max_per_page = 50
- commits = Release.objects.order_by('-date')[:max_per_page]
+ releases = Release.objects.order_by('-version')[:max_per_page]
def prepair(c):
setattr(c, 'int_number', c.number[1:])
- map(prepair, commits)
+ map(prepair, releases)
template = 'nystatus/index.html'
- return render_to_response(template, {'commits': commits},
+ return render_to_response(template, {'releases': releases},
context_instance=RequestContext(request))
View
3  settings_versioned.py
@@ -75,7 +75,8 @@
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.contrib.messages.context_processors.messages",
- "nystatus.context_processors.public_settings")
+ "nystatus.context_processors.public_settings",
+ 'django.core.context_processors.request')
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
Please sign in to comment.
Something went wrong with that request. Please try again.