Django and Horizon deprecation fixes for Pike

This fixes a number of issues which were found during
testing (GBP UI is broken without these fixes):

1. Convert present/past action to methods

Table data_type_singular and data_type_plural attributes are
deprecated. action_present and action_past strings are also
deprecated. They are all being converted here.

Changes made per commit in the Horizon:
c393a6651f

2. Fixes to allow use of Django 1.11
There were some undocumented widgets used in Horizon, and in turn by us,
that were cut from Django 1.11. We are restoring some of the utility
methods that were dropped, but we are still using.

Changes based on the commit in Horizon:
52150749a6

3. Drop Django 1.7 vestiges
url template tag is no longer required to imported from the future template tag library.

Changes based on the commit in Horizon:
f2b2289b4a

Change-Id: I424cdaedd04ef9e887e34a09478e2c572d1ffd78
This commit is contained in:
Sumit Naiksatam 2018-01-26 12:08:39 -08:00
parent 24b6d51aa6
commit 99332501f0
20 changed files with 305 additions and 77 deletions

View File

@ -10,10 +10,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from itertools import chain
from django.core import urlresolvers from django.core import urlresolvers
from django.forms import fields from django.forms import fields
from django.forms import TextInput from django.forms import TextInput
from django.forms import widgets from django.forms import widgets
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.forms.utils import flatatt from django.forms.utils import flatatt
@ -134,6 +137,47 @@ class TransferTableWidget(widgets.SelectMultiple):
return mark_safe('\n'.join(output)) return mark_safe('\n'.join(output))
def build_attrs(self, extra_attrs=None, **kwargs):
"Helper function for building an attribute dictionary."
attrs = dict(self.attrs, **kwargs)
if extra_attrs:
attrs.update(extra_attrs)
return attrs
def render_option(self, selected_choices, option_value, option_label):
if option_value is None:
option_value = ''
option_value = force_text(option_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
return format_html('<option value="{}"{}>{}</option>',
option_value,
selected_html,
force_text(option_label))
def render_options(self, choices, selected_choices):
# Normalize to strings.
selected_choices = set(force_text(v) for v in selected_choices)
output = []
for option_value, option_label in chain(self.choices, choices):
if isinstance(option_label, (list, tuple)):
output.append(format_html('<optgroup label="{}">',
force_text(option_value)))
for option in option_label:
output.append(self.render_option(selected_choices,
*option))
output.append('</optgroup>')
else:
output.append(self.render_option(selected_choices,
option_value,
option_label))
return '\n'.join(output)
# ...this adds the 'add item button' just by existing and returning a # ...this adds the 'add item button' just by existing and returning a
# true-y value # true-y value
def get_add_item_url(self): def get_add_item_url(self):

View File

@ -12,6 +12,7 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables from horizon import tables
@ -39,14 +40,26 @@ class UpdateAppPolicyLink(tables.LinkAction):
class DeletePolicyRuleSetLink(tables.DeleteAction): class DeletePolicyRuleSetLink(tables.DeleteAction):
name = "deletepolicyruleset" name = "deletepolicyruleset"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Policy Rule Set")
data_type_plural = _("Policy Rule Sets")
def action(self, request, object_id): def action(self, request, object_id):
client.policy_rule_set_delete(request, object_id) client.policy_rule_set_delete(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Policy Rule Set",
u"Delete Policy Rule Sets",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Policy Rule Set",
u"Scheduled deletion of Policy Rule Sets",
count
)
class AddPolicyRuleLink(tables.LinkAction): class AddPolicyRuleLink(tables.LinkAction):
name = "addpolicyrules" name = "addpolicyrules"
@ -69,14 +82,26 @@ class UpdatePolicyRuleLink(tables.LinkAction):
class DeletePolicyRuleLink(tables.DeleteAction): class DeletePolicyRuleLink(tables.DeleteAction):
name = "deletepolicyrule" name = "deletepolicyrule"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Policy Rule")
data_type_plural = _("Policy Rules")
def action(self, request, object_id): def action(self, request, object_id):
client.policyrule_delete(request, object_id) client.policyrule_delete(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Policy Rule",
u"Delete Policy Rules",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Policy Rule",
u"Scheduled deletion of Policy Rules",
count
)
class AddPolicyClassifierLink(tables.LinkAction): class AddPolicyClassifierLink(tables.LinkAction):
name = "addpolicyclassifiers" name = "addpolicyclassifiers"
@ -99,14 +124,26 @@ class UpdatePolicyClassifierLink(tables.LinkAction):
class DeletePolicyClassifierLink(tables.DeleteAction): class DeletePolicyClassifierLink(tables.DeleteAction):
name = "deletepolicyclassifier" name = "deletepolicyclassifier"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Policy Classifier")
data_type_plural = _("Policy Classifiers")
def action(self, request, object_id): def action(self, request, object_id):
client.policyclassifier_delete(request, object_id) client.policyclassifier_delete(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Policy Classifier",
u"Delete Policy Classifiers",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Policy Classifier",
u"Scheduled deletion of Policy Classifiers",
count
)
class AddPolicyActionLink(tables.LinkAction): class AddPolicyActionLink(tables.LinkAction):
name = "addpolicyactions" name = "addpolicyactions"
@ -129,14 +166,26 @@ class UpdatePolicyActionLink(tables.LinkAction):
class DeletePolicyActionLink(tables.DeleteAction): class DeletePolicyActionLink(tables.DeleteAction):
name = "deletepolicyaction" name = "deletepolicyaction"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Policy Action")
data_type_plural = _("Policy Actions")
def action(self, request, object_id): def action(self, request, object_id):
client.policyaction_delete(request, object_id) client.policyaction_delete(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Policy Action",
u"Delete Policy Actions",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Policy Action",
u"Scheduled deletion of Policy Actions",
count
)
class ApplicationPoliciesTable(tables.DataTable): class ApplicationPoliciesTable(tables.DataTable):
name = tables.Column("name", name = tables.Column("name",

View File

@ -1,5 +1,5 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">
<dl> <dl>

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -12,6 +12,7 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables from horizon import tables
@ -38,14 +39,26 @@ class EditL2PolicyLink(tables.LinkAction):
class DeleteL2PolicyLink(tables.DeleteAction): class DeleteL2PolicyLink(tables.DeleteAction):
name = "deletel2policy" name = "deletel2policy"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("L2Policy")
data_type_plural = _("L2Policies")
def action(self, request, object_id): def action(self, request, object_id):
client.l2policy_delete(request, object_id) client.l2policy_delete(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete L2 Policy",
u"Delete L2 Policies",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of L2 Policy",
u"Scheduled deletion of L2 Policies",
count
)
class L2PolicyTable(tables.DataTable): class L2PolicyTable(tables.DataTable):
name = tables.Column( name = tables.Column(
@ -87,14 +100,26 @@ class EditL3PolicyLink(tables.LinkAction):
class DeleteL3PolicyLink(tables.DeleteAction): class DeleteL3PolicyLink(tables.DeleteAction):
name = "deletel3policy" name = "deletel3policy"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("L3 Policy")
data_type_plural = _("L3 Policies")
def action(self, request, object_id): def action(self, request, object_id):
client.l3policy_delete(request, object_id) client.l3policy_delete(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete L3 Policy",
u"Delete L3 Policies",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of L3 Policy",
u"Scheduled deletion of L3 Policies",
count
)
class L3PolicyTable(tables.DataTable): class L3PolicyTable(tables.DataTable):
name = tables.Column( name = tables.Column(
@ -138,14 +163,26 @@ class EditServicePolicyLink(tables.LinkAction):
class DeleteServicePolicyLink(tables.DeleteAction): class DeleteServicePolicyLink(tables.DeleteAction):
name = "deletespolicy" name = "deletespolicy"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("ServicePolicy")
data_type_plural = _("ServicePolicies")
def action(self, request, object_id): def action(self, request, object_id):
client.delete_networkservice_policy(request, object_id) client.delete_networkservice_policy(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Network Service Policy",
u"Delete Network ServiceL3 Policies",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Network Service Policy",
u"Scheduled deletion of Network Service Policies",
count
)
class ServicePolicyTable(tables.DataTable): class ServicePolicyTable(tables.DataTable):
name = tables.Column( name = tables.Column(
@ -191,14 +228,26 @@ class EditExternalConnectivityLink(tables.LinkAction):
class DeleteExternalConnectivityLink(tables.DeleteAction): class DeleteExternalConnectivityLink(tables.DeleteAction):
name = "deleteexternalconnectivity" name = "deleteexternalconnectivity"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("ExternalConnectivity")
data_type_plural = _("ExternalConnectivities")
def action(self, request, object_id): def action(self, request, object_id):
client.delete_externalconnectivity(request, object_id) client.delete_externalconnectivity(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete External Connectivity Policy",
u"Delete External Connectivity Policies",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of External Connectivity Policy",
u"Scheduled deletion of External Connectivity Policies",
count
)
class ExternalConnectivityTable(tables.DataTable): class ExternalConnectivityTable(tables.DataTable):
name = tables.Column( name = tables.Column(
@ -228,14 +277,26 @@ class CreateNATPoolLink(tables.LinkAction):
class DeleteNATPoolLink(tables.DeleteAction): class DeleteNATPoolLink(tables.DeleteAction):
name = "deletenatpool" name = "deletenatpool"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("NAT Pool")
data_type_plural = _("NAT Pools")
def action(self, request, object_id): def action(self, request, object_id):
client.delete_natpool(request, object_id) client.delete_natpool(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete NAT Pool",
u"Delete NAT Pools",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of NAT Pool",
u"Scheduled deletion of NAT Pools",
count
)
class EditNATPoolLink(tables.LinkAction): class EditNATPoolLink(tables.LinkAction):
name = "update_nat_pool" name = "update_nat_pool"

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -12,6 +12,7 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from gbpui import client from gbpui import client
from horizon import tables from horizon import tables
@ -37,14 +38,26 @@ class EditServiceChainSpecLink(tables.LinkAction):
class DeleteServiceChainSpecLink(tables.DeleteAction): class DeleteServiceChainSpecLink(tables.DeleteAction):
name = "deletescspec" name = "deletescspec"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Service Chain Spec")
data_type_plural = _("Service Chain Specs")
def action(self, request, object_id): def action(self, request, object_id):
client.delete_servicechain_spec(request, object_id) client.delete_servicechain_spec(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Service Chain Spec",
u"Delete Service Chain Specs",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Service Chain Spec",
u"Scheduled deletion of Service Chain Specs",
count
)
class ServiceChainSpecTable(tables.DataTable): class ServiceChainSpecTable(tables.DataTable):
name = tables.Column( name = tables.Column(
@ -85,14 +98,26 @@ class EditServiceChainNodeLink(tables.LinkAction):
class DeleteServiceChainNodeLink(tables.DeleteAction): class DeleteServiceChainNodeLink(tables.DeleteAction):
name = "deletescnode" name = "deletescnode"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Service Chain Node")
data_type_plural = _("Service Chain Nodes")
def action(self, request, object_id): def action(self, request, object_id):
client.delete_servicechain_node(request, object_id) client.delete_servicechain_node(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Service Chain Node",
u"Delete Service Chain Nodes",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Service Chain Node",
u"Scheduled deletion of Service Chain Nodes",
count
)
class ServiceChainNodeTable(tables.DataTable): class ServiceChainNodeTable(tables.DataTable):
name = tables.Column( name = tables.Column(
@ -134,14 +159,26 @@ class EditServiceChainInstanceLink(tables.LinkAction):
class DeleteServiceChainInstanceLink(tables.DeleteAction): class DeleteServiceChainInstanceLink(tables.DeleteAction):
name = "deletescinstance" name = "deletescinstance"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("ServiceChainInstance")
data_type_plural = _("ServiceChainInstances")
def action(self, request, object_id): def action(self, request, object_id):
client.delete_servicechain_instance(request, object_id) client.delete_servicechain_instance(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Service Chain Instance",
u"Delete Service Chain Instances",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Service Chain Instance",
u"Scheduled deletion of Service Chain Instances",
count
)
class ServiceChainInstanceTable(tables.DataTable): class ServiceChainInstanceTable(tables.DataTable):
name = tables.Column( name = tables.Column(
@ -175,14 +212,26 @@ class CreateServiceProfileLink(tables.LinkAction):
class DeleteServiceProfileLink(tables.DeleteAction): class DeleteServiceProfileLink(tables.DeleteAction):
name = "deleteserviceprofile" name = "deleteserviceprofile"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("ServiceProfile")
data_type_plural = _("ServiceProfiles")
def action(self, request, object_id): def action(self, request, object_id):
client.delete_service_profile(request, object_id) client.delete_service_profile(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Service Chain Profile",
u"Delete Service Chain Profiles",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Service Chain Profile",
u"Scheduled deletion of Service Chain Profiles",
count
)
class ServiceProfileTable(tables.DataTable): class ServiceProfileTable(tables.DataTable):
name = tables.Column( name = tables.Column(

View File

@ -1,6 +1,5 @@
{% extends "horizon/common/_modal_form.html" %} {% extends "horizon/common/_modal_form.html" %}
{% load i18n %} {% load i18n %}
{% load url from future %}
{% block form_id %}create_service_chain_spec{% endblock %} {% block form_id %}create_service_chain_spec{% endblock %}
{% block form_action %}{% url 'horizon:project:network_services:create_sc_spec' %}{% endblock %} {% block form_action %}{% url 'horizon:project:network_services:create_sc_spec' %}{% endblock %}

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info detail"> <div class="info detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
{% load staticfiles %} {% load staticfiles %}
{% block css %} {% block css %}

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">

View File

@ -1,6 +1,5 @@
{% extends "horizon/common/_modal_form.html" %} {% extends "horizon/common/_modal_form.html" %}
{% load i18n %} {% load i18n %}
{% load url from future %}
{% block form_id %}update_service_chain_spec{% endblock %} {% block form_id %}update_service_chain_spec{% endblock %}
{% block form_action %}{% url 'horizon:project:network_services:update_sc_spec' scspec_id %}{% endblock %} {% block form_action %}{% url 'horizon:project:network_services:update_sc_spec' scspec_id %}{% endblock %}

View File

@ -15,6 +15,7 @@ from django.core.urlresolvers import reverse
from django import http from django import http
from django import shortcuts from django import shortcuts
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions from horizon import exceptions
from horizon import tables from horizon import tables
@ -41,14 +42,26 @@ class UpdatePTGLink(tables.LinkAction):
class DeletePTGLink(tables.DeleteAction): class DeletePTGLink(tables.DeleteAction):
name = "deletepolicytarget" name = "deletepolicytarget"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Group")
data_type_plural = _("Groups")
def action(self, request, object_id): def action(self, request, object_id):
client.policy_target_delete(request, object_id) client.policy_target_delete(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Group",
u"Delete Groups",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of Group",
u"Scheduled deletion of Groups",
count
)
class AddPTGLink(tables.LinkAction): class AddPTGLink(tables.LinkAction):
name = "addpolicy_target" name = "addpolicy_target"
@ -102,14 +115,26 @@ class AddExternalPTGLink(tables.LinkAction):
class DeleteExternalPTGLink(tables.DeleteAction): class DeleteExternalPTGLink(tables.DeleteAction):
name = "deleteexternalpolicytarget" name = "deleteexternalpolicytarget"
action_present = _("Delete")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Group")
data_type_plural = _("Groups")
def action(self, request, object_id): def action(self, request, object_id):
client.ext_policy_target_delete(request, object_id) client.ext_policy_target_delete(request, object_id)
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete External Group",
u"Delete External Groups",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Scheduled deletion of External Group",
u"Scheduled deletion of External Groups",
count
)
class ExternalPTGsTable(tables.DataTable): class ExternalPTGsTable(tables.DataTable):
name = tables.Column( name = tables.Column(
@ -163,8 +188,22 @@ class LaunchVMLink(tables.LinkAction):
class RemoveVMLink(tables.DeleteAction): class RemoveVMLink(tables.DeleteAction):
data_type_singular = _("Member")
data_type_plural = _("Members") @staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Member",
u"Delete Members",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Member",
u"Deleted Members",
count
)
def delete(self, request, instance_id): def delete(self, request, instance_id):
url = reverse("horizon:project:policytargets:policy_targetdetails", url = reverse("horizon:project:policytargets:policy_targetdetails",

View File

@ -1,5 +1,4 @@
{% load i18n sizeformat parse_date %} {% load i18n sizeformat parse_date %}
{% load url from future %}
<div class="info row detail"> <div class="info row detail">
<hr class="header_rule"> <hr class="header_rule">