Code

Opened 3 years ago

Last modified 3 weeks ago

#15667 new New feature

Implement template-based widget rendering

Reported by: brutasse Owned by: brutasse
Component: Forms Version:
Severity: Normal Keywords: form-rendering
Cc: carl@…, idan@…, jezdez, ironfroggy@…, poswald, brutasse, trebor74hr@…, gregor@…, JMagnusson, dmclain, mathieu.agopian@…, tom@…, tgecho, dguardiola@…, mmitar@…, philipe.rp@…, Gwildor Triage Stage: Accepted
Has patch: yes Needs documentation: yes
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

Following this proposal on django-dev, this ticket tracks the status of replacing the widgets rendering code with a template-based system.

The proposal is based on an existing implementation, django-floppyforms. The api provides several ways of extending a widget:

  • Widget.template_name: the name of the template used to render the widget
  • Widget.get_context_data(): a way to inject additional context data
  • Widget.get_context(name, value, attrs=None): this method calls get_context_data() and provides the basic context variables: attrs, hidden, name, required, type.

I'm actively working on a patch and will attach it to the ticket as soon as I can so that the implementation and extension points can be discussed.

Attachments (1)

test.py (1.2 KB) - added by akaariai 3 years ago.
Performance test

Download all attachments as: .zip

Change History (39)

comment:1 Changed 3 years ago by carljm

  • Cc carl@… added
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

comment:2 Changed 3 years ago by idangazit

  • Cc idan@… added

comment:3 Changed 3 years ago by jezdez

  • Cc jezdez added

comment:4 Changed 3 years ago by calvinspealman

  • Cc ironfroggy@… added

comment:5 Changed 3 years ago by poswald

  • Cc poswald added

comment:6 Changed 3 years ago by julien

  • Triage Stage changed from Unreviewed to Accepted

This would be a very welcome feature addition, provided that the performance drawbacks would be kept to a minimum (as noted in the django-dev thread).

comment:7 follow-up: Changed 3 years ago by brutasse

  • Cc brutasse added

Right, so I have a patch but it's too large for Trac :). I'll be using a github branch instead:

https://github.com/brutasse/django/compare/15667-template-widgets

All the regressiontest changes are whitespace-related, or changes in the widget attrs ordering (name="foo" value="bar" vs value="bar" name="foo").

A few comments on the curent implementation:

  • ClearableFileInput no longer has its 'template_with_initial' and 'template_with_clear' arguments. This breaks a widget in the admin but I haven't started the admin widget migration yet. The question is whether this is supposed to be a public API or not.
  • The RadioSelect "renderer" API should be deprecated. I left it unchanged (although I added PendingDeprecationWarnings), it still works but it's not doing any template-based rendering. The templates give us enough flexibility so I'm in favor of starting its deprecation.
  • I added a template loader that is added to the list of loaders, this way the templates are always available and they can be overridden in project- or app-provided templates. Template caching works as expected, the cached loader also caches the forms templates.
  • I chose to explicitly add the input's "name" and "type" to the template context instead of leaving it in the "attrs" dictionary. I like it this way but I can put it back in the attrs if we decide to.

TODO:

  • Topic documentation and widget reference (I'll make a draft but I will need help here :)
  • Convert the admin widgets
  • Add tests for the new widgets API (make sure altering template_name, get_context_data and get_context work as expected)

I'll keep reporting my progress here and will update my branch on github. As always, comments welcome :)

comment:8 in reply to: ↑ 7 Changed 3 years ago by carljm

Replying to brutasse:

Right, so I have a patch but it's too large for Trac :). I'll be using a github branch instead:

https://github.com/brutasse/django/compare/15667-template-widgets

This looks great!

  • ClearableFileInput no longer has its 'template_with_initial' and 'template_with_clear' arguments. This breaks a widget in the admin but I haven't started the admin widget migration yet. The question is whether this is supposed to be a public API or not.

They aren't arguments, just class attributes. And they are not documented (I avoided documenting them specifically because I was hoping we could soon get rid of them by moving to template-based widgets). It's probably worth a note in the release notes in this patch for anyone who had a subclass overriding them, but since they aren't documented I don't think we need to provide the full deprecation path for them.

  • The RadioSelect "renderer" API should be deprecated. I left it unchanged (although I added PendingDeprecationWarnings), it still works but it's not doing any template-based rendering. The templates give us enough flexibility so I'm in favor of starting its deprecation.

Agreed.

  • I added a template loader that is added to the list of loaders, this way the templates are always available and they can be overridden in project- or app-provided templates. Template caching works as expected, the cached loader also caches the forms templates.

So I think this is the trickiest part here. I'm hesitant to introduce another implicit coupling between conceptually-separate components of Django, and it makes me cringe a bit that there's now no way to use the Django template language without having this forms template loader added automatically. That said, I don't have any other brilliant ideas for how to make this "just work" transparently when people upgrade. Needs more thought.

On a more minor note, I'm not a big fan of how you're finding the forms_template_dir, relying on the relative filesystem location of django/template/loaders/forms.py and django/forms. I think it might be better to import the django.forms module and use its file attribute?

  • I chose to explicitly add the input's "name" and "type" to the template context instead of leaving it in the "attrs" dictionary. I like it this way but I can put it back in the attrs if we decide to.

I prefer this as well; more explicit.

  • Topic documentation and widget reference (I'll make a draft but I will need help here :)

I'm happy to help with this at some point.

I'll keep reporting my progress here and will update my branch on github. As always, comments welcome :)

Great work, thanks!

comment:9 Changed 3 years ago by trebor74hr

  • Cc trebor74hr@… added

comment:10 Changed 3 years ago by gregmuellegger

  • Cc gregor@… added

comment:11 Changed 3 years ago by lukeplant

  • Type set to New feature

comment:12 Changed 3 years ago by JMagnusson

  • Cc JMagnusson added
  • Severity set to Normal

comment:13 Changed 3 years ago by dmclain

  • Cc dmclain added

comment:14 follow-up: Changed 3 years ago by brutasse

  • Easy pickings unset

Some progress since the last update. The changes can be seen here:

https://github.com/brutasse/django/compare/15667-template-widgets

Carl, I integrated your suggestion to use django.forms.__file__ to locate the forms templates. I have to agree the coupling between the template system and the forms library may be an issue. Maybe this should be discussed on django-dev but the GSoC proposal about form rendering is going to make this coupling even stronger if it gets merged.

Almost all admin widgets have been converted. All tests now pass, I added a test for the admin RadioInput widget. Now for some discussion:

  • the MultiWidget class has a format_output() method that joins the outputs from all of its widgets. The base MultiWidget just does "".join(outputs) but in the admin (see AdminSplitDateTimeWidget) it inserts some markup between the outputs. I'm not sure of the best way to move this to the templates... Remove format_output and make MutiWidget use a higher-level template? Leave it as it is and let the users decide if they want format_output to use the template system or string interpolation?
  • Same for RelatedFieldWidgetWrapper. This basically wraps a widget, gets its output and adds the "Add Another" link next to it. I'd be tempted to make its render() method render the widget a separate template for the "Add Another" button, then join the outputs. Not sure if there are better options.

After these two points, the roadmap is to add tests for the extension points (get_context_data, get_context, template_name) and document it as a public API. Let me know if I overlooked something!

comment:15 in reply to: ↑ 14 ; follow-ups: Changed 3 years ago by carljm

Replying to brutasse:

I have to agree the coupling between the template system and the forms library may be an issue. Maybe this should be discussed on django-dev but the GSoC proposal about form rendering is going to make this coupling even stronger if it gets merged.

I don't think it's acceptable to have this enforced two-way coupling between forms and templates in the long run, but we do need to provide a deprecation-smoothed upgrade path. Here's my proposal:

  1. Add the form-defaults template loader to TEMPLATE_LOADERS in the startproject template settings.py, but not to the global_settings TEMPLATE_LOADERS default.
  2. Document that you need to have the form-defaults template loader listed in TEMPLATE_LOADERS, if you want to have any of Django's default form/widget templates available (we should leave open the option that someone wants to provide all the form/widget templates they will use themselves, and not use any of the defaults built in to Django, though I think this is pretty unlikely in practice).
  3. Have temporary code that automatically adds the form template loader (last in priority order) if it is not listed in TEMPLATE_LOADERS, like you do now.
  4. If the form template loader is not explicitly listed in TEMPLATE_LOADERS and loads a template, have it issue PendingDeprecationWarning. This would result in lots of warnings, which is tricky. I don't want to make the warning module-wide, since, in the case I mentioned above, if people don't need the form-defaults loader because they provide all their own templates, they should be able to escape the warning. So it should only warn if the form loader actually loads a template. We might want to consider using some internal state on the loader to make it a first-time-only warning? I don't really like that, but it's better than hundreds of warnings or issuing the warning at module scope, IMO.
  5. When this deprecation cycle concludes in Django 1.6, remove the automatically-add-form-template-loader code. At this point, if people don't have the form template loader in TEMPLATE_LOADERS they'll just get a TemplateNotFound error for any form template they need but don't provide themselves.

I think this solution would be able to adapt to the increased use of templates in Gregor's GSoC proposal without any trouble.

Does this sound like a reasonable approach? Anything I'm missing?

  • the MultiWidget class has a format_output() method that joins the outputs from all of its widgets. The base MultiWidget just does "".join(outputs) but in the admin (see AdminSplitDateTimeWidget) it inserts some markup between the outputs. I'm not sure of the best way to move this to the templates... Remove format_output and make MutiWidget use a higher-level template? Leave it as it is and let the users decide if they want format_output to use the template system or string interpolation?

I think we should leave format_output() in place for backwards compatibility, and have its default implementation render a MultiWidget template. Moving forward, format_output can become an internal implementation detail, and overriding the template becomes the documented way to customize MultiWidget rendering.

  • Same for RelatedFieldWidgetWrapper. This basically wraps a widget, gets its output and adds the "Add Another" link next to it. I'd be tempted to make its render() method render the widget a separate template for the "Add Another" button, then join the outputs. Not sure if there are better options.

I wouldn't implicitly join the outputs of two different templates; I'd have a related-field-widget-wrapper template that takes the wrapped widget's rendered HTML in its context and can wrap it however it wants.

comment:16 in reply to: ↑ 15 ; follow-up: Changed 3 years ago by brutasse

Replying to carljm:

Replying to brutasse:

I have to agree the coupling between the template system and the forms library may be an issue. Maybe this should be discussed on django-dev but the GSoC proposal about form rendering is going to make this coupling even stronger if it gets merged.

I don't think it's acceptable to have this enforced two-way coupling between forms and templates in the long run, but we do need to provide a deprecation-smoothed upgrade path. Here's my proposal:

  1. Add the form-defaults template loader to TEMPLATE_LOADERS in the startproject template settings.py, but not to the global_settings TEMPLATE_LOADERS default.
  2. Document that you need to have the form-defaults template loader listed in TEMPLATE_LOADERS, if you want to have any of Django's default form/widget templates available (we should leave open the option that someone wants to provide all the form/widget templates they will use themselves, and not use any of the defaults built in to Django, though I think this is pretty unlikely in practice).
  3. Have temporary code that automatically adds the form template loader (last in priority order) if it is not listed in TEMPLATE_LOADERS, like you do now.
  4. If the form template loader is not explicitly listed in TEMPLATE_LOADERS and loads a template, have it issue PendingDeprecationWarning. This would result in lots of warnings, which is tricky. I don't want to make the warning module-wide, since, in the case I mentioned above, if people don't need the form-defaults loader because they provide all their own templates, they should be able to escape the warning. So it should only warn if the form loader actually loads a template. We might want to consider using some internal state on the loader to make it a first-time-only warning? I don't really like that, but it's better than hundreds of warnings or issuing the warning at module scope, IMO.
  5. When this deprecation cycle concludes in Django 1.6, remove the automatically-add-form-template-loader code. At this point, if people don't have the form template loader in TEMPLATE_LOADERS they'll just get a TemplateNotFound error for any form template they need but don't provide themselves.

I think this solution would be able to adapt to the increased use of templates in Gregor's GSoC proposal without any trouble.

Does this sound like a reasonable approach? Anything I'm missing?

I like that, I agree it's much better than 2-way coupling. I'll have a look at the warnings and see how verbosity can be avoided.

  • the MultiWidget class has a format_output() method that joins the outputs from all of its widgets. The base MultiWidget just does "".join(outputs) but in the admin (see AdminSplitDateTimeWidget) it inserts some markup between the outputs. I'm not sure of the best way to move this to the templates... Remove format_output and make MutiWidget use a higher-level template? Leave it as it is and let the users decide if they want format_output to use the template system or string interpolation?

I think we should leave format_output() in place for backwards compatibility, and have its default implementation render a MultiWidget template. Moving forward, format_output can become an internal implementation detail, and overriding the template becomes the documented way to customize MultiWidget rendering.

So a MultiWidget gets a template attribute? (it currently doesn't have any). By default it'd be {% for output in rendered_widget %}{{ output }}{% endfor %} . If it gets implemented like this I'd say format_output() can be deprecated in favor of the API other widgets already have (get_context(), etc), unless there is a use case for format_output() that can't be implemented using a template. That'd be mostly for consistency if we want a MultiWidget to have the same customization hooks as every other widget.

  • Same for RelatedFieldWidgetWrapper. This basically wraps a widget, gets its output and adds the "Add Another" link next to it. I'd be tempted to make its render() method render the widget a separate template for the "Add Another" button, then join the outputs. Not sure if there are better options.

I wouldn't implicitly join the outputs of two different templates; I'd have a related-field-widget-wrapper template that takes the wrapped widget's rendered HTML in its context and can wrap it however it wants.

Ok, I'll do it this way. Thanks for your feedback :)

comment:17 Changed 3 years ago by magopian

  • Cc mathieu.agopian@… added

comment:18 in reply to: ↑ 16 Changed 3 years ago by carljm

Replying to brutasse:

  • the MultiWidget class has a format_output() method that joins the outputs from all of its widgets. The base MultiWidget just does "".join(outputs) but in the admin (see AdminSplitDateTimeWidget) it inserts some markup between the outputs. I'm not sure of the best way to move this to the templates... Remove format_output and make MutiWidget use a higher-level template? Leave it as it is and let the users decide if they want format_output to use the template system or string interpolation?

I think we should leave format_output() in place for backwards compatibility, and have its default implementation render a MultiWidget template. Moving forward, format_output can become an internal implementation detail, and overriding the template becomes the documented way to customize MultiWidget rendering.

So a MultiWidget gets a template attribute? (it currently doesn't have any). By default it'd be {% for output in rendered_widget %}{{ output }}{% endfor %} .

Yes, I that seems to me like the most consistent and flexible option.

If it gets implemented like this I'd say format_output() can be deprecated in favor of the API other widgets already have (get_context(), etc), unless there is a use case for format_output() that can't be implemented using a template. That'd be mostly for consistency if we want a MultiWidget to have the same customization hooks as every other widget.

Yes, I agree that format_output could probably start a deprecation path. It's a little bit tricky - what you actually want to deprecate is a user-defined subclass relying on format_output being called. I'd need to take a closer look at the code to see the best way to do this.

I'm looking forward to getting this in; Gregor's GSoC on form rendering will depend on some of this (particularly the form template loader), and I don't want to hold up his progress (if necessary we can just apply this patch in his branch -- I'd rather do that than apply this to trunk before it's ready -- but it would simplify things if we just had this in trunk). If you have a latest-state-of-the-patch with some tasks remaining to be done, let me know and I'll see if I can help.

comment:19 Changed 3 years ago by brutasse

Ok, so according to the chat we had on IRC with Carl an Jannis, format_output and renderer / get_renderer should be deprecated since it's too hard to keep them along with the new API and they're not considered public. This is done in the latest version of the patch:

https://github.com/brutasse/django/compare/15667-template-widgets

If a MultiWidget defines a format_output() method, calling render() raises a DeprecationWarning and uses template-based rendering anyway. Same with the renderers:

  • providing renderer as a kwarg during RadioSelect instanciation raises DeprecationWarning
  • the RadioSelect class doesn't have a renderer attribute anymore
  • any call to get_renderer() raises a DeprecationWarning and returns RadioFieldRenderer
  • RadioSelect's render() will always use templates

The admin's RelatedFieldWidgetWrapper now has its template attribute and get_context method.

The template loader still needs to be worked on (Carl's comment 15 above). I'll give it a go tomorrow night, CEST.

Changed 3 years ago by akaariai

Performance test

comment:20 Changed 3 years ago by akaariai

I did some performance testing using default settings of 1.3.0, except that caching template loader was enabled.

I had two test, in the first one I had a a form with 11 integer fields. using ipython %timeit, I got 1.76ms per loop with 1.3.0, and 3.72ms per loop with code downloaded from GitHub. This seems good enough.

The second test is more worrisome. In this test I had 11 MultipleChoiceFields, each having 100 choices. The result without this patch was 25ms per loop, with the patch 160ms per loop. With just 1 field with 100 choices the results were 2.27ms vs 14.6ms. So, when using a 100 choice field the performance difference is 6.5x. Performance test code attached.

comment:21 Changed 3 years ago by akaariai

Just to add some more info, I tried template based widgets with Jinja2 (version 2.1.1). I got very encouraging results, though it might be that I am doing something wrong as the results seem to be too good to be true. I got 1.47ms with Jinja2 vs 2.27 with Django 1.3.0. Most likely the speed difference is because Jinja2 does not localize or escape some of the values in the template, while the 1.3.0 implementation does. I haven't looked more into this.

The conclusion that can be drawn from this example is that if template compilation with comparable speeds to Jinja2 can be included in Django, then this feature needs not be slower than current implementation at all.

For completeness, here are the steps needed to reproduce the jinja2 rendering test. I added the following code into widgets.py:

from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader('foobar', 'templates'))

def jinja_render_to_string(context):
    template = env.get_template("select.html")
    return template.render(**context)

and I added a directory foobar/templates/ to my testing root folder with select.html in it (you need to change attrs.items to attrs.items() in the first line of the template). Context is gotten by changing return Context(context) to return context in Widget.get_context(). Then, Select.render() just calls jinja_render_to_string(context).

comment:22 in reply to: ↑ 15 Changed 3 years ago by brutasse

Replying to carljm:

I don't think it's acceptable to have this enforced two-way coupling between forms and templates in the long run, but we do need to provide a deprecation-smoothed upgrade path. Here's my proposal:

  1. Add the form-defaults template loader to TEMPLATE_LOADERS in the startproject template settings.py, but not to the global_settings TEMPLATE_LOADERS default.
  2. Document that you need to have the form-defaults template loader listed in TEMPLATE_LOADERS, if you want to have any of Django's default form/widget templates available (we should leave open the option that someone wants to provide all the form/widget templates they will use themselves, and not use any of the defaults built in to Django, though I think this is pretty unlikely in practice).
  3. Have temporary code that automatically adds the form template loader (last in priority order) if it is not listed in TEMPLATE_LOADERS, like you do now.
  4. If the form template loader is not explicitly listed in TEMPLATE_LOADERS and loads a template, have it issue PendingDeprecationWarning. This would result in lots of warnings, which is tricky. I don't want to make the warning module-wide, since, in the case I mentioned above, if people don't need the form-defaults loader because they provide all their own templates, they should be able to escape the warning. So it should only warn if the form loader actually loads a template. We might want to consider using some internal state on the loader to make it a first-time-only warning? I don't really like that, but it's better than hundreds of warnings or issuing the warning at module scope, IMO.
  5. When this deprecation cycle concludes in Django 1.6, remove the automatically-add-form-template-loader code. At this point, if people don't have the form template loader in TEMPLATE_LOADERS they'll just get a TemplateNotFound error for any form template they need but don't provide themselves.

1) and 3) are done on the github branch, I'm not sure about the right way to implement 4) while making it consistent across the cached and non-cached template loaders... I'll dig a bit more but I'm open to suggestions.

comment:23 Changed 3 years ago by brutasse

  • Has patch set
  • Needs documentation set

I just pushed the code that warns if the forms template loader has been automatically added:

https://github.com/brutasse/django/commit/b76dd323ce48e5e38061d59954990951653bfa0b

The missing bit is now documentation, unless there is something fundamentally wrong with the patch.

comment:24 Changed 3 years ago by carljm

I'll try to find some time soon to review this in depth, write up some docs, and see if there's anything we can do to bring some of the worst-case performance numbers into better shape. Thanks for all the work on the patch!

comment:25 Changed 3 years ago by brutasse

I generated some profiling graphs of the benchmarks here:

http://media.bruno.im/render_it.png
http://media.bruno.im/render_it2.png

I was wondering if the {% if %} check in the selectmultiple widget was a bottleneck but looking at the graphs it doesn't look so. Not sure about what can be done, the {% if %} check accounts for about 1% of the rendering time. Most of the stuff going on is variable resolving and this closed loop on the render_it2 graph, there is some circular stuff here because of the {% for %} loop I guess.

comment:26 Changed 3 years ago by akaariai

I have a feeling there is not much that can be done in this ticket to improve the speed of MultipleChoiceField (or ChoiceField for that matter). Current implementation of Django templates is just a bit slow. Maybe it would be best to bring this up in django-developers for design decision? Or mark this ticket design decision needed? As I see it, there are three ways forward:

  1. Accept the performance loss. For some users of 1.3 this can be a decision which makes their current application unusable in 1.4.
  2. Decide that the performance loss is unacceptable. In this case, this ticket should wait for a faster implementation of Django template rendering. Personally, I have high hopes that the GSOC project of Armin Ronacher will achieve this.
  3. Keep the default implementation of the performance-problematic widgets in Python. One could still use template based rendering for these widgets, but by default the python implementation would be used. This is a bit uqly, but would allow this feature to get in without big worries of performance problems for users upgrading to 1.4.

One small thing that could theoretically have an impact on performance is the usage of context.update in get_context subclass methods. This is creating a new stack entry in the context which can slow down variable resolution. But as choices are in the upmost stack entry, this should not have a big impact. Still, it would be IMHO cleaner if there would be just one stack entry in the context given to the template.

comment:27 Changed 3 years ago by carljm

  • Keywords form-rendering added
  • Patch needs improvement set
  • UI/UX unset

Currently, the most up-to-date version of this patch lives in the 2011 GSoC form-rendering branch. The original GSoC branch is at https://github.com/gregmuellegger/django/compare/master...soc2011%2Fform-rendering and I've been making some further updates in https://github.com/carljm/django/compare/master...soc2011%2Fform-rendering

That branch combines the templated widgets with a new form-rendering API. The problem with the branch is still speed, and since it doesn't appear we're going to get speed improvements in the template language in the short term from Armin's GSoC, we need to look for other solutions. My planned next step is to split back out just the templated-widgets from the form-rendering branch in order to focus on seeing if we can improve that to the point where we can commit it and close this ticket. Then we can deal with the rest of the form-rendering API in a separate step.

There's also a bit of a framework for speed comparisons using djangobench here: https://github.com/carljm/formrenderbench

comment:28 Changed 3 years ago by jacob

  • milestone 1.4 deleted

Milestone 1.4 deleted

comment:29 Changed 2 years ago by carljm

This patch would also fix #16630. Not closing that as duplicate because it's more narrowly focused and can be done separately.

comment:30 Changed 2 years ago by tomchristie

  • Cc tom@… added

comment:31 Changed 2 years ago by tgecho

  • Cc tgecho added

comment:32 Changed 2 years ago by quinode

  • Cc dguardiola@… added

comment:33 Changed 2 years ago by mitar

  • Cc mmitar@… added

comment:34 Changed 18 months ago by chronos

  • Cc philipe.rp@… added

comment:35 Changed 15 months ago by akaariai

My opinion: if we want this, lets implement the default rendering in Python. We could also provide default templates and compare output in testing so that they produce the same result.

This isn't beautiful. But this way those who need absolute speed can get it. In most of cases ultimate speed isn't a requirement.

Using template based forms does make so much sense that in my opinion the code duplication is worth it.

comment:36 Changed 3 months ago by Gwildor

  • Cc Gwildor added

comment:37 Changed 2 months ago by Gwildor

Are there any updates on this subject? In my humble opinion the widgets are one of the ugliest parts of Django codewise and could logically learn a lot from how the CBV's work. Most render() methods are a bit of a mess, and this makes creating custom widgets which subclass an existing widget, even subclassing something like Input, one of the hardest parts of the general things to do with the framework. Why has nothing happened in the past two years?

comment:38 Changed 3 weeks ago by chrismedrela

See the lengthy discussion about introducing out-of-the-box support for Jinja2 on django-developers: https://groups.google.com/forum/#!topic/django-developers/Bk-22bKqCTo.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as new
The owner will be changed from brutasse to anonymous. Next status will be 'assigned'
as The resolution will be set. Next status will be 'closed'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.