shabda
Comments
Reactions

A response to Dropping Django

By : Shabda Raaj

Brandon Bloom yesterday wrote an interesting post titled dropping Django. Despite a lot of hand waving(We needed a pragmatic template language to replace Django's idealistic one.), it raises some valid questions, so here is a solution to some of them.

No support for hierarchical url creation.

The simplest representation of nested urls I can think of is a nested tuple. Lets represent, the urls for a simple app by,

>>> tree_urls = ('', 'list',
...     ('edit/', 'edit', ('auto/', 'edit_auto')),
...     ('^add/', 'add'),
...     ('delete/', 'delete', ('hard/', 'delete_hard'))
...     )

Guess what, urls.py is just a python module which excepts a variable names urlpatterns.

Which means it is very easy to write a function which converts this nested structure to flat, structure. Here is a quick attempt at that,

def merge(url):
    full_url=[]
    for i, el in enumerate(url):
        if i%2==0:    
            full_url.append(el)
    full_url = ''.join(full_url)
    return full_url

def combineflatten(seq):
    items= tuple(item for item in seq if not isinstance(item, tuple))
    yield items
    for item in seq:
            if isinstance(item, tuple):
                for yielded in combineflatten(item):
                    yield items+yielded

def generate_flat_urls(tree_urls):
    """
    >>> tree_urls = ('', 'list',
    ...     ('edit/', 'edit', ('auto/', 'edit_auto')),
    ...     ('^add/', 'add'),
    ...     ('delete/', 'delete', ('delete/', 'delete_hard'))
    ...     )

    >>> generate_flat_urls(tree_urls)
    [('^$', 'list'), ('^edit/$', 'edit'), ('^edit/auto/$', 'edit_auto'), ('^^add/$', 'add'), ('^delete/$', 'delete'), ('^delete/delete/$', 'delete_hard')]
    """
    return [('^%s$'%merge(el), el[-1]) for el in tuple(combineflatten(tree_urls))]

With this you can use hierarchical urls in urls.py as,

#All of urls.py
tree_urls = ('', 'list',
    ('edit/', 'edit', ('auto/', 'edit_auto')),
    ('^add/', 'add'),
    ('delete/', 'delete', ('delete/', 'delete_hard'))
    )

flat_urls = generate_flat_urls(tree_urls)

urlpatterns = patterns('app.views',
**flat_urls
)

No support for per user, per resource authorisation.

If you want to do this in a almost no-touching-the-existing-code way, replace all your render_to_response with,

def render_with_auth_check(request, payload, request_context, *args, **kwrags):
    for el in payload.itervalues():
        try:
            has_auth = el.check_auth(request.user)
            if not has_auth:
                raise AuthFailException
        except ValueError:
            pass #Not all objects have check_auth
    return render_to_response(request, payload, request_context, *args, **kwrags)

And enable this middleware,

class AuthFailHandlerMiddleware:
    def process_exception(self, request, exception):
        if type(exception) == AuthFailException:
            return render_to_response('accounts/login/', {}, RequestContext(request))

This assumes that all resources which are authorisation protected have a .check_auth, but I cant see any way round that in any other way as well.

A different, and more robust way would be to write custom managers for all resources, which need authorization.

class ResourceAuthManager(models.Manager):
    def get(self, user, *args, **kwargs):
        res = super(ResourceAuthManager, self).get(*args, **kwargs)
        try:
            has_auth = res.check_auth(request.user)
            if not has_auth:
                raise AuthFailException
        except ValueError:
            pass #Not all objects have check_auth

    ...

The Django templating language is too constrained.

Despite believing that Django templating language hits the sweet spot between, power and convenience to people who would be using it, I never understood this argument,

If you are using sqlalchemy with Django you lose the admin, what do you lose if you use Jinja?

In particular what do you lose by replacing render_to_response with this,

def render_jinja_to_response(template_name, payload):
    #This should probably go in settings.py
    from jinja2 import Environment
    env = Environment(loader=PackageLoader('app', 'templates'))

    template = env.get_template(template_name)
    response = template.render(**payload)
    return HttpResponse(response)

Extending authentication - In particular enable loggin in with email

Django authenticates against a wide range of backends, including Email, LDAP, Twitter and Facebook. While it is true that email backend doesn't work with Admin login, Admin is meant for use by staff and superusers, so why cant we provide them usernames?

There are a few other questions raised, some of which I agree with("Sadly, it[the admin app] struggles a little bit with nullable fields and is tricky to customize."), and some which I dont, ("I will never write CSS by hand again." - You shouldn't be, someone else on your team should be doing that. ) But this line is interesting Personally, I've developed a moderate fear of the word "framework".

This is arguable, but the way all frameworks essentially help you is by providing Dependency Injection, (the Hollywood principle - Dont call me, I will call you). Django provides that in a lot of ways. (You write a middleware, and django calls it at appropriate places - Dont call me, I will call you), but still leaves something to be desired. My ideal framework would be one where models are POPO and views are functions which return strings . (Plain old python objects -like POJO). JPA does this correctly, but then JPA has the typing information, so maybe in python the only way to provide the required typing information is name=models.CharField(...). But the point being, we need better Dependency Inversion, not less of it. We have been dow that path earlier, and it is a much harder way to build complex systems.


Resources

  1. This code on github
  2. Discussion about converting the nested tuple to a flat one

My apologies if this post was aggressive, argumentative or disrespectful to the original author. We generally try to stay away from controversial posts on this blog. But I believe that this post raised some valid technical questions, and the intent of this post was to answer them. For Django to develop the best way, we need more of these kind of "What is wrong with Django" posts, not less, so my thanks to the original author. :)


Do you want to build Amazing Web Apps? We can help.


Related Posts


Can we help you build amazing apps? Contact us today.

Topics : django rambling tips

Comments

Hang 20th Aug., 2009

I have to agree with you on this since in my experience extending Django is fairly easy (part of that comes from Python I think). You give good examples to illustrate the point. I do, however, wonder if the examples don't exactly fit his situation. Maybe his app is more complicated but if that's the case, I'm not sure how applicable his article is for the rest of the community.

commmenttor
jart 20th Aug., 2009

Are you sure that ResourceAuthManager manager works? Wouldn't you need to enable a middleware that saves the request object to a thread local global variable and then override get_query_set to return add your own filter based on what you would allow a particular user to see?

I wish there was a better way to do this. There is a project called django-authority but I don't think it really helps with this particular type of hack.

commmenttor
Adam

I've spent the last 4 years writing a Python web framework which is now a fully functioning CMS which I've now shelved and decided I'll use Django for anything I do now.

Why not use my own? While mine is pretty secure and fast, I don't want to be maintaining the framework AND the websites I build with it. I build toy websites for my own amusement and Django is great for giving me a leg up there.

However, I would say I'm glad I wrote my own, since I understand why the Django developers do things in a certain way since I've seen and overcome the problems first hand.

Now the original guy original post was saying how they're hardly using any of Django any more and it's all their own bespoke stuff, I'm sure it's all great and been tested etc, but that means they now have to support the middleware as well as the application.

To me that seems like making more work for yourself.

commmenttor
dude 20th Aug., 2009

DAMN Python is fugly.

commmenttor
Brandon Bloom

Hi Shabda,

Several others have shown interest in our approach URL routing to authorization. It seems I wasn't clear enough about how or why our system works. Your solutions don’t fit our needs exactly. What I came up with isn’t much more complex than what you’ve got. I'll try to write a follow-up post within the next week or two.

In short, my stance on the template language debate is: we don't have dedicated designers and we don't expect to have any soon. Django wasn't made for programmers, so we're using something that was.

As for username authentication, the problem is that the default User model and schema enforce a unique, not-null constraint. All users must have a username. It is far from an insurmountable problem: I overrode the save function, called super.save, set username = id, then called save again.

I didn't always just throw out Django, I made it work for me. In some cases, it was faster, easier, and produced better results to just rewrite things. I was always picking the path of least resistance.

Cheers,

-Brandon

commmenttor
Andrew Shultz

I know a friend of mine is getting around the email for username thing by assigning a fake username (generating a UUID for it) and then making the login form take email and password instead of usename and password. He did a lot of authentication customization (including LDAP integration) in the configuration tool we worked on together in django as well.

commmenttor
Vinay Sajip 21st Aug., 2009

Doing auth checks in the render phase seems like an anti-pattern to me - a lot of work may have been done getting data for the response. Instead, the check should be done at the start of the view, and perhaps syntactically sugared with a suitable decorator (similar to login_required).

commmenttor
Mads Sülau Jørgensen

The django admin actually does auth against email, but only if you write in your email in the username field, and only if there is only 1 user with that email. It'll tell you, that you cannot login with your email, if no or >1 users with that email exists.

It's some tricky business.

http://code.djangoproject.com/browser/django/tags/releases/1.1/django/contrib/admin/views/decorators.py#L58

commmenttor
Andrew Ingram

By switching to jinja2, you lose generic views. You also can't use another template language to extend existing templates - like the django admin templates.

So far at work, despite having a dedicated designer, the programmers have been building all the templates and here's why:

A template author has to understand the dot notation for traversing the object tree, this means they need to KNOW the object tree and how it works in relation to the dot notation. Say I have a variable 'n', and I want to access the nth object in an array 'foo' in the template, a designer might incorrectly try to access foo.n, instead they'd have to ask the programmers to write a filter so they can write foo|arrayget:n or something similar. When in jinja2 you can just write foo[n].

Whenever a template designer needs to access a method on an object that would normally take parameters, they have to either ask the coder to refactor the context and models or to write a custom tag (which is a very untidy process). This seems to be doing the opposite of seperating design from presentation - I've found myself adding numerous methods to my models and hacking getattr simply for the purpose of making some small template use cases possible.

Just because something is in a template, it doesn't mean it doesn't need advanced logic. A template designer using Django is going to have to get their toes wet with Python as soon as they start doing anything beyond a very simple blog app.

Now here's the kicker: The Django template language is no easier to learn than Python (because Python is famous for its readability and ease of use/learning), but it does introduce a horrendous stack trace for debugging, an unneccessay burden on the programmers to modify context, and write filters or tags. Not to forget the godawful syntax for conditionals, just to get around writing an equality in Python.

The reason jinja2 is nice is that it's superficially a superset of Django's own template language, a template designer can use the designer-friendly syntax as far as possible - but dip into the 'advanced' syntax for those rare occasions when they need something more.

commmenttor
Cornelis 24th Aug., 2009

I do agree with Andrew, although I'm still sticking with the Django template language. On several occasions I have to access filtered related records for the current object. I have to write a separate tag for that every time with all the maintenance overhead attached were it would take three lines of template code if Django would only allow object.related_set.filter(whatever) instead of only object.related_set.all inside templates.

commmenttor
Have Always Dreamt Of Designer Discount Sunglasses

A response to Dropping Django - Agiliq Blog | Django web app development

commmenttor

Reactions

shabda

http://bit.ly/4hc9ca New post. A response to Dropping Django@montylounge Read the dropping django link via your post yesterday. Opinions?

This comment was originally posted on Twitter

uswaretech

http://bit.ly/4hc9ca A response to Dropping Django.

This comment was originally posted on Twitter

hackernewsbot

A response to Dropping Django… http://bit.ly/ET4I0

This comment was originally posted on Twitter

Davertron

‘There are a few other questions raised, some of which I agree with(”Sadly, it[the admin app] struggles a little bit with nullable fields and is tricky to customize.”), and some which I dont, (”I will never write CSS by hand again.” – You shouldn’t be, someone else on your team should be doing that.)’Ugh, this is just a totally unrealistic and out-of-touch argument. Look, I work on a website, and it’s me and one other guy, and we both have to write frontend and backend code. It’s not even remotely reasonable for us to just stop writing frontend stuff and tell our boss to hire someone else to do it. I fully understand why the Django templating language is the way it is, and it even makes sense, but it’s not perfect for all situations, one of which is when the user is a big boy and can handle the responsibility of using full-blown python in a template.

This comment was originally posted on Hacker News

jjames

The implication that designers are not "big boys" is offensive. I will parry with: "big boys" often wield power they don’t understand to theirs and everyone else’s detriment. More importantly, if all the big boys get hit by a machismo bus who will be around to cook the spaghetti?

This comment was originally posted on Hacker News

tvon

The admin not allowing email addresses as usernames still irks me, such a seemingly pointless restriction in this day and age.Decent response to the original article, though I have to admit that I have a pet peeve about developers declaring libraries/frameworks "insufficient", "lacking" or "fundamentally flawed" as an excuse to write things themselves. Or maybe I’m just grumpy.

This comment was originally posted on Hacker News

tvon

I didn’t pick up that implication, I took it to mean that there is no reason to "neuter" (subjective) a template language just because the expected audience is designers.

This comment was originally posted on Hacker News

mrtron

If you want to use full blown python in a template – use a template tag.What scenario do you require python in a template? I am a big boy, and the few times I have thought about needing python in a template I realized I need to do a bit more work in the view.

I think it is a great example of protecting ‘big boys’ from themselves. A year from now when you don’t remember how you designed a section of your code you can trivially debug and test instead of trying to sort through logic in the presentation layer.

Also, it makes unit testing much easier having logic stop at the views.

This comment was originally posted on Hacker News

newsycombinator

A response to Dropping Django http://bit.ly/18pfuT

This comment was originally posted on Twitter

bcurtu

A response to Dropping Django http://bit.ly/5nwv8

This comment was originally posted on Twitter

forsaken

Good designers seem to think that it is perfectly reasonable:http://twitter.com/nathanborror/status/3416472007

This comment was originally posted on Hacker News

flogic

That’s always confused me about template languages. They require knowledge of coding but don’t include the full power of a traditional language. In a way it’s the worst of both worlds. If you’re designer is a only a Frontpage or Dreamweaver jocky, you’re still hosed. If they grasp coding, why limit them?

This comment was originally posted on Hacker News

Teifion

A good response, good enough that I’m adding them to my RSS reader.

This comment was originally posted on Reddit

scott_s

I think the parent’s use of "big boy" was ironic; if someone is patronizing me, I might say "Listen, I’m a big boy, I can handle it." I don’t see it as, necessarily, saying I’m in a class above others. Just that I can handle the responsibility someone else thinks I can’t.

This comment was originally posted on Hacker News

twoodham

@uswaretech provides solutions to django “limitations”. Interesting read. http://icio.us/5bjhxn

This comment was originally posted on Twitter

jjames

I thought this was meant to disagree with my statement. This status message doesn’t say Programmers = Big Boys, Designers = (What would you call it? little boys, girls, hobos?).There is power in a template paradigm. Much like MVC is for people who "can’t handle" hacking their entire program into a single executable, convention (guided suggestions or limitations) can do a lot for a group of developers even if they aren’t collaborating with designers. If they don’t recognize that, so be it (they will) but calling convention a softball for the less manly application developer is a crudely un-meditated suggestion.

This comment was originally posted on Hacker News

antonovka

The point isn’t to limit the designer, but to constrain complexity creep.Additionally, a good template language does not require knowledge of coding. I’ve had considerable success with providing front-end HTML writers with templates based on purely declarative custom HTML/XML tags.

Programmers write and document the tag libraries, HTML developers use them, and the HTML templates themselves are simple, easy to read, and generally declarative.

This comment was originally posted on Hacker News

Davertron

The whole point is that I don’t need to be protected from myself. I understand the benefits of an MVC architecture, so I don’t need to be limited by the templating language. Just because I want to use python in my template doesn’t mean I’m going to start throwing database accesses in there…

This comment was originally posted on Hacker News

Davertron

Yeah, I wasn’t trying to suggest that I’m above designers or anything. In fact, I really respect good designers, because I’m not a great designer and I know how hard it is. However, the article is basically saying "Look, you shouldn’t even be writing CSS, you’re a coder! Coders code! You should hire someone else to do that!" This just seems a bit naive to me.

This comment was originally posted on Hacker News

snprbob86

(I wrote the original Dropping Django post)I wrote a quick comment at http://uswaretech.com/blog/2009/08/a-response-to-dropping-dj…;

Maybe I wasn’t clear enough, or maybe this is just guaranteed flame war territory. But I think this comment sums up what I was trying to say better than anything: http://news.ycombinator.com/item?id=773510

I’m going to try to write more about our URL routing and authorization soon.

This comment was originally posted on Hacker News

pyry

To summarize this blog post: if Django doensn’t do it out of the box, it’s certainly not out of the question to be creative and come up with a solution oneself. Isn’t that what’s fun about being a programmer?

This comment was originally posted on Reddit

scott_s

I think the point you’re missing is that no one was calling convention a "softball for the less manly application developer."

This comment was originally posted on Hacker News

LeGrandOiseau

More than that: Django’s components are not all that tightly coupled, so if you don’t like something (say, the templating engine), you can use another one without inordinate pain. Note the very small amount of code needed to so fill in some of the gaps. Also note that in at least one case (replacing render_to_response) you could make a decorator that would reduce that overhead even further.

This comment was originally posted on Reddit

satans_little_coder

This article only confirms what the original writer was talking about – Django has to be hacked in unconventional ways to make it do things that should be simple. Who the hell wants to hack into render_to_response just to do per page authentication? that’s the most bizarre place you could possibly put auth code.

This comment was originally posted on Reddit

joshsharp

A reponse to the article about dropping Django I posted yesterday: http://is.gd/2qITg

This comment was originally posted on Twitter

webstartupgroup

StartupNews: A response to Dropping Django http://bit.ly/2vdPZR

This comment was originally posted on Twitter

bad_user

> logic in the presentation layerSometimes logic belongs in the presentation layer (if it is related to showing the information to the user).

Take the "if" tag for example. It doesn’t support complex conditions (values can only be tested for values of truth, and it doesn’t support parenthesis to define precedence) and adding an if block inside another if block is just ugly. To get around this, you usually construct a boolean value inside your view, but it often happens that this value is only relevant for one template (i.e. HTML). And if your view also returns JSON / YAML, that’s not clean.

Also, QuerySets are lazy by default, and it goes beautifully hand in hand with caching template fragments, and this works for me since I usually can’t cache whole pages (some fragments are more dynamic than others). But if you move template logic in your view, then you’re going to have to worry about caching in your view too, duplicating logic.

Also, the if tag doesn’t support "ifelse" … what’s up with that? Are designers too stupid to use ifelse properly?

Yes, I know you can create your own tags, but it would’ve been nice if these things worked out of the box, and it’s not OK to define a tag that’s not reusable (like in the case of a complex "if" condition that can’t be expressed in the template directly), because that’s extra work that doesn’t pay off.

And since we are talking about designer-friendliness, how are designers going to understand working with such non-standard tags all over the place?

Now, thus far the Django templates haven’t been much of a problem for me, but the projects I worked on in Django have been pretty simple. But I know they are not enough so I’m currently looking for alternatives (fortunately it is easy to replace).

This comment was originally posted on Hacker News

bad_user

> logic in the presentation layerSometimes logic belongs in the presentation layer (if it is related to showing the information to the user).

Take the "if" tag for example. It doesn’t support complex conditions (values can only be tested for values of truth, and it doesn’t support parenthesis to define precedence) and adding an if block inside another if block is just ugly. To get around this, you usually construct a boolean value inside your view, but it often happens that this value is only relevant for one template (i.e. HTML). And if your view also returns JSON / YAML, that’s not clean.

Also, QuerySets are lazy by default, and it goes beautifully hand in hand with caching template fragments, and this works for me since I usually can’t cache whole pages (some fragments are more dynamic than others). But if you move template logic in your view, then you’re going to have to worry about caching in your view too, duplicating logic.

Also, the if tag doesn’t support "ifelse" … what’s up with that? Are designers too stupid to use ifelse properly?

Yes, I know you can create your own tags, but it would’ve been nice if these things worked out of the box, and it’s not OK to define a tag that’s not reusable (like in the case of a complex "if" condition that can’t be expressed in the template directly), because that’s extra work that doesn’t pay off.

This comment was originally posted on Hacker News

redvasily

Have you ever tried replacing Django template engine yourself? That’s very easy only on the surface. I used Jinja2 in several Django projects and of course you can easily replace render_to_response() or use something like render_to() decorator, but there are other problems. All standard or third-pary modules use their own render_to_reponse() or even load_template() so you either patch those libraries or resort to monkeypatching. Or you can patch your django and have to merge your changes while Django is updated. There are other problems, like if you need to add some custom functionality in the admin and want to use the template which is already used on your site, but your template doesn’t work with admin app, because it quite obviously uses Django admin. Well, I can’t really blame Django admin for that, it’s unreasonable to expect it to include a template in the different language, but anyway, you’ve got a problem. So I stopped using Jinja for Django projects, while it’s deffinetely a nicer template language it doesn’t worth all the troubles. I have a feeling that all people saying: "You can use different templating system in Django" never tried it in the real-life project. Please stop saying that. I think it’s taking the whole discussion about Django template system in the wrong dirrection, like: – Hey guys why don’t you make a template language more powerfull. – STFU, go import jinja2 No you can’t just "import jinja2" in Django, and that’s the whole point of all this complainting about Django template system.

This comment was originally posted on Reddit

bad_user

> Additionally, a good template language does not require knowledge of codingI don’t think I ever saw an effective template language that doesn’t require programming knowledge. The only exception is when you move all logic in tags, but even then logic gets mixed with presentation (just not in the main template), and the downside is that a tag is like a black-box … if the scope is not clear, you have no way of knowing what it produces by not looking over its code (CSS files in ASP.NET end up containing general classes in most cases because of that).

That’s because the presentation layer requires logic, for example … if this happens, then show this, else show that. Or to show the breadcrumb, iterate through this list. Or is this DateTime value in UTC? Then show it in the user’s locale. That’s logic and it’s a lot more then a template with holes in it to fill.

And where is this logic supposed to go if not in the presentation layer?

All the designers I worked with also knew a programming language (at a superficial level at least) and where quite capable of coding complex logic in Javascript (which is also part of the presentation layer). Our fear is that designers don’t understand a more complex language, but how many of you worked with such people? (since web-design is all about the end-product, I can’t imagine a good designer that can’t handle logic). And even if the language guards against shooting yourself in the foot you can always find creative ways to fuck up.

This comment was originally posted on Hacker News

bad_user

> Additionally, a good template language does not require knowledge of codingI don’t think I ever saw an effective template language that doesn’t require programming knowledge. The only exception is when you move all logic in tags, but even then logic gets mixed with presentation (just not in the main template), and the downside is that a tag is like a black-box … if the scope is not clear, you have no way of knowing what it produces by not looking over its code (CSS files in ASP.NET end up containing general classes in most cases because of that).

That’s because the presentation layer requires logic, for example … if this happens, then show this, else show that. Or to show the breadcrumb, iterate through this list. Or is this DateTime value in UTC? Then show it in the user’s locale. That’s logic and it’s a lot more then a template with holes in it to fill.

And where is this logic supposed to go if not in the presentation layer?

All the designers I worked with also knew a programming language (at a superficial level at least) and where quite capable of coding complex logic in Javascript (which is also part of the presentation layer). Our fear is that designers don’t understand a more complex language, but how many of you worked with such people? (since web-design is all about the end-product, I can’t imagine a good designer that can’t handle logic). And if the language guards against shooting yourself in the foot, as long as it is turing-complete, you can always find creative ways to fuck up.

This comment was originally posted on Hacker News

Davertron

"Yes, I know you can create your own tags…"Yeah, this is usually the response you get when you mention stuff like the missing ifelse, and it’s totally ridiculous. Why should I go about reinventing this stuff? The whole reason I’m using a framework is to speed up development. You also risk everyone inventing it on their own in their own way, so now if you go try to work on someone else’s project, maybe they’ve implemented it, maybe they haven’t, and even if they have, it’s probably not going to be the same way you implemented it, so now you’re re-learning again for no added benefit.

This comment was originally posted on Hacker News

IsaacSchlueter

I’m not a python or django developer, so I can’t comment on the technical merits of what either of you are saying. However, this exchange has left me with a lot of respect for both of you. It’s been very polite and professional, and hasn’t fallen into the usual vitriol and holy wars.

This comment was originally posted on Hacker News

disc_links

HT@ayharano: A response to Dropping Django http://bit.ly/n26PN

This comment was originally posted on Twitter

delicious50

A response to Dropping Django — The Uswaretech Blog – Django Web Development http://bit.ly/9yOp9 web python programming dev django

This comment was originally posted on Twitter

Codebender

> Lets represent, the urls for a simple app by, … > … a python module which excepts a variable names urlpatterns. > …converts this nested structure to flat, structure. This is painful to read. The author seriously needs to learn to write and proof-read. Lesson #1: if there’s any doubt, you don’t need that comma.

This comment was originally posted on Reddit

disc_links

HT@ayharano: A response to Dropping Django http://bit.ly/n26PN

This comment was originally posted on Twitter

the_guv

A response to Dropping Django — The Uswaretech Blog – Django Web Development http://bit.ly/5nwv8
.. ^guv

This comment was originally posted on Twitter

© Agiliq, 2009-2012