Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
bitprophet
Jul 22, 2004
Taco Defender

Puddy1 posted:

I'm new to django and python so go easy on me here, but what I'm trying to do is run some code whenever a model is saved to the database, although I don't know how to do that. Is there some sort of method I can overwrite when I save this particular model?

http://www.djangoproject.com/documentation/model-api/#overriding-default-model-methods

edit: BONUUUUUUUUUUS :argh:

Adbot
ADBOT LOVES YOU

bitprophet
Jul 22, 2004
Taco Defender
Moving to NFA is not actually that hard, I've been using it for a while and had to rewrite my entire book's code examples to conform to it, a few months ago. Didn't take that long, considering.

It's basically just replacing every inner Admin class with either a one-line "registration" of your model with the default admin site (if you did the "class Admin: pass" bit) or the one-line registration plus an external <Model class name>Admin class with whatever options you had in your inner class.

The nice thing is that once you get used to this way of doing things you realize how much neater it is, and it's only one or two extra lines of code per model class, if that, depending on how many admin options you need. This plus the older newforms setup of having distinct ModelForm classes really refactors things so that your models, as chunks of code, are distinct from the forms and/or admin settings that relate to them.

bitprophet
Jul 22, 2004
Taco Defender
That seems like an odd bug to me; what version are you using, 0.96 or some recentish SVN checkout? I would not be surprised if that was something extant in 0.96 that's been fixed more recently.

If it's still around in SVN I'd say it was just overlooked, so look real hard for an existing Trac ticket about it (chances of one existing for something like this are high) and then add a note or patch to that ticket if none exists, or make a new ticket w/ patch if there isn't a ticket yet. Then, of course, feel free to patch your local version until that patch is accepted and merged to trunk :)

bitprophet
Jul 22, 2004
Taco Defender
What you want is called "inline editing" and in NFA it's handled with a few admin-related classes. See here for an example, I can't be arsed to find any official docs for it at the moment (not sure if/how any official docs for NFA were merged into the live site docs).

So you just want to make an inline class for Address, and then mention it in an 'inlines' attribute of the admin class for Volunteer, and you should be all set.

vvv Aha, that's where they stuck it, thanks. Will have to read it over to see how well it's improved over the old "docs" aka "a couple of infrequently updated wiki pages" :)

bitprophet fucked around with this message at 13:33 on Jul 28, 2008

bitprophet
Jul 22, 2004
Taco Defender
In general, anything having to do with "code" implicitly means you can't use it with generic views, since generic views are what they are (namely a way to AVOID writing actual code). In order to use commit=False you'd need to write a wholly custom view for editing, which isn't that bad (it's maybe ten lines of code or so).

It's usually an either-or decision like this; the only time you can mix generics with custom code is if your intended logic can fit around the generic view somehow (such as writing a view which sets up a QuerySet you can't built in a URLconf, and then calls the generic view with that QS, which does the rest of the work).

The big thing about Django, then, is knowing what to use when; when to use the super shortcuts like generics or the admin, vs using the (still very rapid, thanks to the ORM and the forms library and so forth) approach of custom views.

bitprophet fucked around with this message at 02:35 on Jul 29, 2008

bitprophet
Jul 22, 2004
Taco Defender

MonkeyMaker posted:

I'm using ModelForms for most of my...forms. Anyway, I'd like to wrap required fields in one fieldset and non-required fields in another. Anyone know of any simple way to do this?

You mean aside from manually specifying them with fields? (or is it fieldsets now? can't remember which way it changed)

It might be possible to programmatically do it, i.e. define an __init__() to introspect the model's field attributes and (over)write the instance's fields attribute, but that sort of thing doesn't always work well given the metaprogramming surrounding Django classes. Then again, I don't think ModelForm is used anywhere but in your own custom code, which lessens the chance of there being some behind-the-scenes admin related magic or some such. Hard to tell without trying it :)

bitprophet
Jul 22, 2004
Taco Defender
Yes, it sounds like you have it backwards. Here's code based on what I think you just said your setup was:
code:
class Story(models.Model):
    title = models.CharField(max_length=100)

class Character(models.Model):
    name = models.CharField(max_length=100)
    story = models.ForeignKey(Story)
In that scenario -- which is the wrong way to model it, and I'll get to that in a sec -- Character objects would have a .story attribute resulting in a single Story object, and Stories would have a .character_set attribute which is a Manger allowing you to do e.g. .all(), or .add(), to work with the related Character objects. That's how ForeignKey works.

Now, my example code there is a setup where a character can only belong to one story (and, since it's many-to-one, a story can thus have >1 character). I'm guessing you do want ManyToManyField, because a character can often belong to >1 story in addition to stories being made up of >1 character.

Thus, you'd do something like this:

code:
class Character(models.Model):
    name = models.CharField(max_length=100)

class Story(models.Model):
    title = models.CharField(max_length=100)
    characters = models.ManyToManyField(Character)
and then Story.characters is an (explicit) Manager, as is Character.story_set (an implicit Manager created by the ManyToManyField). You can also have a more "readable" or "obvious" setup by specifying the name of the implicit manager, like so:

code:
    characters = models.ManyToManyField(Character, related_name='stories')
and then you'd have Character.stories instead of .story_set.

Note also that M2M is totally arbitrary as to which side of the relationship you define it on -- you could just as easily put a 'stories' ManyToManyField on Character instead, and have the exact same result (assuming you used related_name='characters').

bitprophet
Jul 22, 2004
Taco Defender
Running Django from trunk is generally very stable bug-wise, but they've never claimed that it would be stable feature-wise, especially now that they're ramping up to 1.0 and thus need to break as much backwards compatibility as is necessary so they don't have to do it after 1.0.

http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges

I think the one you're specifically looking for is here:

http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Filestoragerefactoring

bitprophet
Jul 22, 2004
Taco Defender
I'm not sure it's that strong a Django convention; I certainly never do it that way, instead preferring to use http://mydomain.com/media/css/ or similar. However, the core Django team does do it this way -- have a separate subdomain for media files -- and the way Django is set up makes it pretty easy to do this.

Keeping your media files within your Django project, by the way, is definitely a convention, as it lets you keep everything in the same place. One usually then symlinks the media directory/ies to the actual Web server's docroot so they are statically served. I think that's what you're seeing, but can't tell for sure without seeing what else is in the directory structure you mentioned.

EDIT: OK, yea, I think you posted the literal output of `ls` there? Those are symlinks as I mentioned. So, yes, that particular thing -- just having a couple symlinks in your docroot and nothing else -- is definitely a convention and a good way of doing things.

Honestly I'd be more worried that Plesk is involved :v:

bitprophet fucked around with this message at 16:48 on Aug 16, 2008

bitprophet
Jul 22, 2004
Taco Defender
What he said. Python is generally very explicit: it only knows about the names you've told it about. So in any random .py file, such as your views.py, you can only refer to classes or functions or modules that you've explicitly imported.

Do you come from a Rails background? Ruby/Rails is much less explicit and has a lot more magic, which could definitely cause a bit of confusion if you're then new to Python.

bitprophet
Jul 22, 2004
Taco Defender

deimos posted:

I can't say I like this presentation better than the other. The old version was a bit clearer to me. Took me a while to even begin looking for the templates reference. (And there are like 5 links for templates)

More gripes:
Still no official signals documentation.
Still no clear "views" docs other than the stuff intermixed in the tutorial.
Still no middleware dev docs.


I do like that it has breadcrumbs now and that the template docs are slightly more organized.

My understanding was that they didn't actually add any new docs with this effort, simply switching to a new docs generator, tweaking the presentation a bit, and rearranging some material. That said, yea, I'm not sure I like it either, but hopefully that's just me being used to the old layout :(

Signals: agreed, wish they'd document it already, they're very useful in some situations.

There are clear "views" docs, the deal is that views are just Python functions...ones that take in requests and return responses, and thus the request/response docs are what you actually want.

Middleware dev docs: right here :)

bitprophet
Jul 22, 2004
Taco Defender

evilmonkeh posted:

I've been passing data to templates in urls.pl like:

'extra_context' : {
'news_articles': Article.objects.all().order_by('date')[:5]
}

but when the news articles are updated, the site isn't. How do I get around this? it only happens on the live server (using apache) and not on the built in dev server.

Thanks!

URLconfs are only loaded once, when the server starts, and therefore any values calculated within them will only be executed that one time. This is commonly seen when you do e.g. {'timestamp': datetime.datetime.now()} for a context dict, and then find out the timestamp never changes :)

So, the only "safe" values to set in URLconfs are ones guaranteed to truly yield their value later on, when called in the related view and/or template...like QuerySets, which are lazily evaluated: they only actually hit the DB and return results at the last possible moment, such as when you iterate over them...or when you slice them. Which is what you did in your code!

All you need to do is have your news article template do the slicing instead of the URLconf, using the 'slice' template filter, and things should work.

If this doesn't make sense, we can explain it further :)

bitprophet
Jul 22, 2004
Taco Defender
Nice job :)

The dict stuff, I can't see any other way to write it that's not equally verbose or more so. It'd be nice if you could make a dict via dict(otherdict, newkey=newval, newkey2=newval2) (i.e. combining the two behaviors of the dict() factory builtin instead of it being mapping-object-or-keys) but this really isn't too bad.

bitprophet
Jul 22, 2004
Taco Defender
They're correct. The whole deal with Django 1.0 is that after 1.0, they'll be trying very hard to maintain backwards compatibility, such that users do not have to worry about stuff breaking when they upgrade to e.g. Django 1.1.

Because of this, they've been breaking stuff left and right before 1.0 comes out, since they obviously can't do that nearly as much afterwards. Ergo, we've seen huge sweeping changes such as aforementioned newforms-admin, and lesser (but still backwards incompat) changes such as minor alterations to the file upload API.

The semi canonical source for What's Broken is the BackwardsIncompatibleChanges page on the Django wiki. Give it a close read if/when you upgrade.

bitprophet
Jul 22, 2004
Taco Defender

Space Kimchi posted:

Hmm. I'll take a look at that link. I'm not using newforms admin but on the bright side, I don't use the old one very much either, just for a few types of data objects that only need simple CRUD interfaces. Hopefully that will make things simple.

It's not just the admin -- if you're still using oldforms at all, that's out the window now (but good riddance!). Hopefully not, I think regular newforms hit trunk like a year+ ago, and it's just newforms-admin that hit more recently. It's all a blur at this point, I've seen so many big changes to the codebase I can't keep it straight :downs:

Again, just skim down that list on the wiki page, it'll give you a good idea.

bitprophet
Jul 22, 2004
Taco Defender

Senso posted:

It's RIGHT THERE in my PythonPath!

Senso posted:

Even [my] env variable is set correctly...

Your PythonPath and shell environment != Apache's PythonPath and shell environment. Two totally different things.

That said, since you were adding the project directory (and, I assume /home/mlan/proj/project was your Django project root, and not /home/mlan/proj, otherwise that would be another problem) to Apache's PythonPath in your conf, I think NSW is correct, it was probably permissions.

For example, even if your project has the right permissions, if your home directory does NOT, you're still hosed. So you'd need to make sure that www-data has +rx on /home, /home/mlan, and /home/mlan/proj, as well as the project dir.

Moving it to /opt probably solved that problem for you (and fwiw it's nicer to keep stuff in /opt or /srv anyways :)).

bitprophet
Jul 22, 2004
Taco Defender

Senso posted:

It probably was permissions after all. But the thing is, the django folders in /opt and in /home/mlan have the same permissions...

Did you read the last couple sentences of my post? :) even if the Django folders have the same perms, that doesn't mean jack if the parent directories are more restrictive (at least, in my experience and double-checking I did before posting to make sure I wasn't misremembering...).

Senso posted:

I also defined it in bash, just to make sure.

If you're really a Linux sysadmin by trade, you should probably brush up on how the system works, to a decent level :( Your bash environment is in no way connected to the environment Apache runs in, unless you're running in something like sudo -u www-data /bin/bash, and even that isn't necessarily going to give you the 100% same environment, unfortunately.

bitprophet
Jul 22, 2004
Taco Defender
First off, you seem to be using Django 0.96, you should almost definitely get 1.0 which just came out. Gigantic differences. 0.96 is approaching two years old at this point.

Second, unique_together (if it's still valid, I don't recall if it got axed/changed since then) should be in your Meta class. The only stuff that's not a function or inside Admin/Meta classes, are fields, and I don't think you mean to have a field called "unique_together" :)

bitprophet
Jul 22, 2004
Taco Defender
What king_kilr said: if you have other fields you're not showing us on EntryCategory, then you want to have a ManyToManyField on Entry or Category, pointing at the other one, and with through=EntryCategory or similar.

If all you have with EntryCategory is just those two foreign keys, then ditch that and just use a normal ManyToManyField (without the through argument).

Then, if you had the M2M on Entry, like categories=ManyToManyField(Category), you'd do something like this:
code:
entries = Entry.objects.filter(categories__slug="foo")
Might be slightly off base (it might be category__slug) but that's the general idea.

bitprophet
Jul 22, 2004
Taco Defender
code:
                prepopulate_from("title",),
Please explain what this line is doing, and why it differs from the others around it, and hopefully that will clue you in on what's wrong :)

bitprophet fucked around with this message at 23:53 on Sep 5, 2008

bitprophet
Jul 22, 2004
Taco Defender

The Real Ambassador posted:

That's a magical feature from 0.96 that will automatically fill in your field based on what is entered into the title field. I believe they took that out of 1.0 so please steer away from it.

This is true, but not what I was getting at, he has a pretty silly syntax error :)

bitprophet
Jul 22, 2004
Taco Defender

Idimmu posted:

as new as i am to python the error suggests that the prepopulate_from function is undefined, the only 'interesting' thing I can see is the , after title because prepopulate_from takes a tuple/list?

should i be doing something more like:

something = prepopulate_from ..?

im happy to lose it anyway now seeing as its deprecated!

Close, but not quite! Let's look at the code again:
code:
class Tag(models.Model):
    slug = models.SlugField(
        'Slug',
        prepopulate_from("title",),
        help_text='Automatically built from the title.',
        primary_key='True'
    )
    title = models.CharField('Title', maxlength=30)
Notice that most of the other arguments to your Field subclasses (SlugField and CharField) are of the form x=y (i.e. primary_key='True', maxlength=30, etc)? You're missing that for prepopulate_from. It should read prepopulate_from=("title",).

In other words, because you're lacking the equals sign before the tuple, and because tuples happen to be defined with parentheses, you've inadvertently created a function call, hence the error. You want to be saying "the prepopulate_from argument should be given this tuple as its value", which is why you need an equals sign.

vvv Good catch, that one went right over me :downs:

bitprophet fucked around with this message at 21:12 on Sep 6, 2008

bitprophet
Jul 22, 2004
Taco Defender

FrontLine posted:

I haven't tested this yet but I'll trust that it works... although to be honest it looks weird. In any case if no one posts a better solution I'll run with it.

Git posted the other, slightly shorter method -- the overall point is that the shell environment your Python code executes in, must have DJANGO_SETTINGS_MODULE='myproject.settings' (with appropriate value for myproject of course) defined somewhere, somehow. There's no other way for Django to know which settings file to use.

Additionally, myproject needs to be in your Python path in order for it to resolve when imported by Django. Some people symlink their project into Python's existing site-packages directory; others, like The Real Ambassador, tweak the path at runtime.

TRA's example does the sys.path.append('..') which will work if your script is at the same level as your Django project folder; if that is not the case, you'll need to use an absolute path, i.e. sys.path.append('/home/username/django/') if your project folder is /home/username/django/myproject.


Finally, a silly nitpick, you should never name Python modules (folders) with capital letters, as you seem to have done with your 'News' app, it's bad form. The convention is to use lowercase for just about everything except class names, which get CamelCased.

bitprophet
Jul 22, 2004
Taco Defender
I don't see anything functionally wrong (though I've never used staticserve, always just go straight to using Apache) but your code could use a lot of cleanup, allow me to nitpick :)

code:
urlpatterns = patterns('',
	# Media:
	(r'^media/', mediaIndex),

	#crap snipped out
)
You seem to be using 8-space indents -- the Python standard is 4 spaces. Plus that makes it much easier not to have super duper long lines when you're a few indent levels in. I definitely suggest setting your editor to 4-space indents.

code:
if settings.DEBUG == True:
No need to do this: just do if settings.DEBUG: :)

code:
	urlpatterns += patterns('',(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}),)
This is my main complaint, this big ol' one-liner makes it really hard to see what your code is doing (especially if it's in a narrow container like a quote tag). Try formatting it more like the others, like so:

code:
urlpatterns += patterns('',
    (r'^static/(?P<path>.*)$', serve, {
        'document_root': settings.STATIC_ROOT
    }),
)
I just double checked the docs and your URL line's contents still look OK, although I'm curious what settings.STATIC_ROOT is set to. You're not getting any errors in the output of the runserver or anything? Have you double checked permissions (just in case settings.STATIC_ROOT doesn't have correct read/execute permissions for the user running runserver)?

bitprophet
Jul 22, 2004
Taco Defender

m0nk3yz posted:

I wish you for to help me make better django!

I keep forgetting you're Jesse Noller, and so I'll see your blog post in my feed (assuming it's the Django community feed, either that or Planet Python) and then run across a post here a few hours later and do a double-take :)

Looks like a neat app (from reading the main google code page -- it's not really something in the vein of CruiseControl, is it? because it's designed to track manual testing as well as automatic) and it sounds like you're definitely trying to do things The Right Way, which is always great!

I can indirectly recommend James' book, especially after reading his DjangoCon slides, which rocked. Plus I know him from #django and he's a smart cookie.

I'd also recommend my book but it's not quite out yet v:shobon:v and we don't go into the philosophy as heavily as James probably does (ours being more of a well-rounded book instead of focusing solely on app design).

bitprophet
Jul 22, 2004
Taco Defender
The main problem with that sort of thing is that at the most basic level, every hit to the site consists solely of three things: the request object, a context dictionary containing some data, and the template (meaning the specific template called, plus any other templates it extends and/or includes).

In other words, your outermost template doesn't have "its own view" -- it's gonna get whatever context dict is passed to the templates extending it. Here's a quick illustration:

Base template (note the user welcome + content block):
code:
<html>
<body>

<!-- nav -->
<div id="nav">
Welcome, {{ user.name }}!
<ul>
<li><a href="/foo">Foo</a></li>
<li><a href="/bar">Bar</a></li>
</ul>
</div>

<!-- content -->
<div id="content">
<h1>Our Site</h1>
{% block content %}{% endblock %}
</div>

<!-- pretend there's a footer here -->

</body>
</html>
Inner template for the 'foo' section of the site, say foo.html:
code:
{% block content %}
<h2>Foo Content</h2>
<p>{{ content }}</p>
{% endblock %}
Finally, here's your view function for /foo/, ignoring for now that you could do this with a generic view and not need an actual function :)
code:
from django.shortcuts import render_to_response

def foo(request):
    return render_to_response('foo.html', {'user': request.user, 'content': 'foo'})
Phew. The end result of all this, and hitting http://mywebsite.com/foo/ is that you'll get both templates rendering as if they were a single, unified template, with the one context dict provided (which gives the user from the request, and the content string). So your base template will happily render the user's name, and the inner template, the content string.

Now, you probably already knew most of that, but my point here is to illustrate the fact that the base template will render only what it is given each time it's used, and so at a naive level, you have to make sure that (for example) request.user is passed in to every. single. one. of your render calls, or else your base template will say "Hello, !" and look silly. There is no "view logic for the base template".


Thankfully, there's still a better solution than having to make sure you pass the same info in to every single view, or do the same logic in every view, etc, and that's to use what's called context processors, essentially middleware functions that apply to (almost) every template rendering, and can add stuff to the context dict.

For example, if you wanted all templates to have easy access to a {{ section }} variable that determined what section of the site you're in -- such as "foo" or "bar" -- you could do this (ignore the specific code if it's not obvious -- point being that it takes the URL and does some string processing on it):
code:
def add_section_string(request):
    section = ''
    parts = request.path.split('/')[1:]
    if len(parts):
        section = parts[0]
    return {'section': section}
If you stick that in a file and point to it in settings.TEMPLATE_CONTEXT_PROCESSORS, it'll get applied to template render calls that want to use this feature (all generic views, or passing a special context subclass to e.g. render_to_response -- see my docs link below for details). Thus, you could then update your base template to do stuff like:

code:
<ul>
<li{% ifequal section "foo" %} class="active"{% endifequal %}><a href="/foo">Foo</a></li>
...
</ul>
Note that I used section there, and yet, we did not modify the view function any -- we only added a context processor. If you had a big ol' site and all the rendering used the correct context subclass (pretty easy to do, and a good practice in general), plopping in a new context processor will instantly update all your templates with the new information.


Details on context processors can be found here.

bitprophet fucked around with this message at 02:35 on Sep 14, 2008

bitprophet
Jul 22, 2004
Taco Defender

Mashi posted:

A followup question if I may: If I add context processors, they are going to get executed regardless of whether my final output actually requires them (even in the admin interface... Actually I find it odd that your Django site and admin interface share settings).

That's correct, although I wouldn't be too surprised if the admin managed to "turn off" custom context processors, given that it is otherwise generally self-contained. But, yea, it's just another app (albeit a contrib one, and one with perhaps one or two remaining 'hooks' into the framework -- but it's far, far more removed than it used to be) and so it shares the same settings as everything else does. Think of it more like a third-party app you just happen to be including in your site, and less like a core part of the framework, and that makes more sense.

quote:

In some circumstances there might be somewhat considerable computation going on to generate this context, as well. Is it a good habit to subclass Context so that you can apply your own context processors to match the output?

I'm actually not sure -- I haven't seen it done, but I don't have an omniscient view of the entire framework or what people have done with it, so I may well have missed that.

I think a better approach would be to include logic in your context processor that skips the heavy processing when it's not needed; you have access to the request object, which means you can do stuff like
code:
def crazy_processor(request):
    if request.path.startswith('/specific/section/'):
        foo = do_some_crazy_logic()
        return {'foo': foo}
    return {}
That's likely to be a much better idea than tinkering with Context, but the source is open and generally well written, so if you examine RequestContext and see how it manages TEMPLATE_CONTEXT_PROCESSORS, you might be able to make a similar subclass (or a subclass of RequestContext itself, depending) that lets you do whatever you want, maybe something like having a more in-depth context processor mapping. It's all just Python, so go crazy.

bitprophet
Jul 22, 2004
Taco Defender

Wulfeh posted:

Alright, got another problem that I am not quite sure how to approach.

This basically screams for Ajax; I actually did something very similar with my first major Django app, where the list of potential objects one could select on a form was HUGE and would not work as a dropdown, thus I had to make one of those find-as-you-type deals, which of course uses JS callbacks to the server.

If I read you correctly, that's what you want; you could work around the use of Ajax by saving temporary data about the form, but that can get super messy super fast, so Ajax is (IMO) absolutely a better approach.

bitprophet
Jul 22, 2004
Taco Defender

Wulfeh posted:

Another clincher is that we try to stay as far away as possible from AJAX / JS calls to the DB. Reason is because we are diligent in being handicap friendly. This means screen readers have to be able to go through this and be A-Ok at reading what's going on.

In that case, then yea, you're going to have to do what I think you're describing, w/r/t using the DB to preserve some state during form fillout. Kind of a pain, but that's the breaks with HTTP, isn't it :(

bitprophet
Jul 22, 2004
Taco Defender

duck monster posted:

The alternative is mod_perl, or writing a C handler. gently caress that. mod_python is goofy, but it works. This isnt a content handler, its an authentication handler.

The only other way I can see it being done is to write a custom PAM handler using, I dunno, something.

To be fair, use of WSGI, with any server (i.e. mod_wsgi with Apache, or whatever the nginx equiv is) is becoming more popular lately, and a decent number of people also use FCGI.

But, deimos' personal bad experiences with it aside (assuming I remember the source of his complaints correctly :)), mod_python is still the "gold standard" for now, with mod_wsgi being the next closest, I believe. Many people claim WSGI can get them better memory usage, and it (along with FCGI) can get you better security as well, so it's definitely worth a look.

bitprophet
Jul 22, 2004
Taco Defender

dimebag dinkman posted:

Is there an elegant approach - either in the standard system that I've missed, or with a recommendable extension - to handling submit buttons that post back to the same page, and are intended to cause some minor action while otherwise just redrawing the page?
<snip>

I think you want to look into FormSets, they're designed to basically be collections of identical Forms, so in this case you'd make a ItemForm with a single text field, and a FormSet of ItemForms, and then maybe a form containing that FormSet plus the "entry" text field (although I am not too up on exactly how you use/compose FormSets with Forms -- I just know that a FormSet == 0..N identical Forms).

If it's not possible to "compose" FormSets within Forms, then you'd just want the FormSet plus manual stuff for the entry field (which would not be much at all, the tiny HTML snippet plus controller logic to look for request.POST['new_entry_field_lol'] and then make a new object or Form out of it.

bitprophet
Jul 22, 2004
Taco Defender
I spent a loooong time writing up some general theory about this (hinging on how Django is "just Python" and you can therefore just write a function to call when you want this logic applied -- it would take in the request and return a dictionary to be merged into your context dict for the template render) but then realized the answer is even simpler:

Just write a Form class :downs: (that :downs: is me, not you)

The trick is that a Form will happily gobble up any dictionary whatsoever, and will only be interested in the form fields and/or validation methods you've defined on it -- it ignores everything else. Thus, for any arbitrary form submission (including none at all, of course, which is an empty dict) you can make e.g. a ReverseForm expecting your text_to_reverse field, and have its `clean()` method modify the contents of `self.cleaned_data` (see here if you don't already know about it).

In other words, Forms can act as "filters" of a sort on the data they represent. Here's an example for your reversal situation. First, the Form:

code:
from django import forms

class ReverseForm(forms.Form):
    text_to_reverse = forms.CharField(max_length=100, required=False)
    
    def clean(self):
        ttr = self.cleaned_data['text_to_reverse']
        if ttr:
            self.cleaned_data['text_to_reverse'] = ttr[::-1] # No string.reverse :(
        return self.cleaned_data
A potential view:
code:
from myproj.myapp.forms import ReverseForm
from django.shortcuts import render_to_response

def some_random_view(request):
    context = {}
    do_some_shit_with_the_request()
    maybe_update_the_context_who_knows()
    context['reverse_form'] = ReverseForm(request.POST.copy())
    render_to_response('some_template.html', context)
And a potential template:
code:
<html>
<!-- poo poo goes here -->
<form action="" method="post">
{{ reverse_form.text_to_reverse }} <input type="submit" value="press me lol" />
</form>
<!-- other poo poo here -->
</html>
The resulting render will spit out the <input type="text" value="whatevs" /> where the "whatevs" would either be empty (in case of first hit to page) or empty again (if user submitted some other form on the page) or contain the reversed string (if the user filled out and submitted that particular form).

Finally, you can plop that one line in the view -- the context['reverse_form'] = ReverseForm(request.POST.copy()) -- into any view anywhere and it will Just Work. You can obviously make multiple Forms to use in this manner if you've got multiple types of logic to run; the main thing differentiating them from one another is the field or fields to expect, and the logic performed in the "validation" step (though you are, of course, not really validating per se, just modifying the data if it's there).

bitprophet fucked around with this message at 02:36 on Oct 2, 2008

bitprophet
Jul 22, 2004
Taco Defender

The Real Ambassador posted:

Also what is up with the new documentation layout? It's harder to navigate and they couldn't even create 301 redirects from the old URLs. Tsk tsk...

Yea, I understand that Sphinx makes for generally easier to manage documentation, but man I do not really like the new docs layout at all; and my favorite habit of using my browser to search the top level page for the topic of interest, no longer works half the time :( I keep meaning to bring this up with the community since I don't remember seeing anything about it on the mailing lists; I fear everyone who finds it confusing is assuming they're alone and that's why it's not been brought up. Or I'm just missing something :v:

vvv I need to get better at doing that, myself, or add it to Quicksilver's list of Web searches.

bitprophet fucked around with this message at 22:21 on Oct 8, 2008

bitprophet
Jul 22, 2004
Taco Defender

hitze posted:

I've been trying to figure out how to do this for a few weeks now. I've got an ImageField in an admin form, and what I want to do is move that file and rename it with the slug generated in the form. I've searched high and low trying to find a solution to this, but I've only found stuff relating to user uploads, not uploads in the admin area. Does anyone know how to go about doing this? :downs:

What do you mean by "slug generated in the form"? Is it a SlugField or something? Is the ImageField in the same form (i.e. load up empty form => specify both slug and image file => save form => expect renamed/moved image file to be on server now)?

This sounds like a job for overriding your model's save() method, assuming that this slug you speak of ends up back in the model at some point :)

Something like this (going from memory with the os, shutil commands, so double check those for sure; and you'll need to do a bit of tweaking about the image file extension because I am too lazy to look up how that works right now):
code:
import os, shutil
from django import models

def MyModel(models.Model):
    slug = models.SlugField()
    image = models.ImageField()

    def save(self):
        super(MyModel, self).save() # To do the normal saving stuff
        dst = '/path/to/destination/directory/'
        desired = '%s%s.jpg' % (dst, self.slug)
        if not os.path.exists():
            shutil.move(self.image.path, desired)

bitprophet
Jul 22, 2004
Taco Defender

the talent deficit posted:

Is there a generally accepted method of subclassing/extracting the admin app so that you can provide selective access to certain tasks without exposing admin directly? I know about permissions, but frankly they are not nearly flexible enough for what I want to do.

Depends on what exactly you want to do; with the newer admin (the one in 1.0, previously known as newforms-admin) you can create multiple different admin sites with different functionality. Can you give a specific example of what you want to accomplish?

bitprophet
Jul 22, 2004
Taco Defender
Zip file contents, there's a zip library in the Python stdlib, IIRC; thumbnailing, you can use PIL, which you should already have installed since you're using ImageFields (these require PIL). PIL.Image has a thumbnail or resize method, check their docs :)

Also, you should probably make use of os.path.exists() (which I had in my example but apparently forgot to put in the actual argument, oops) because otherwise someone re-saving the object will get a nasty error when your mkdir call fails :) which I believe it will do if the directory already exists.

bitprophet
Jul 22, 2004
Taco Defender

nbv4 posted:

hi I'm a Django nub. I got everything installed and I ran "sudo django-admin startproject myproject" but I ran it in my /var/www folder, which apparently is not a good idea. If I just manually move the folder to my home directory or something, will everything be OK, or will I mess poo poo up? In other words, does that command just simply create those files, or is anything else being done?

It just creates the files, so no worries. There is one aspect of setting up a Django project that cares about where your files are -- namely making sure your project is on Python's include path -- but 'startproject' doesn't do that, so at this point it doesn't really matter.

So, yes, move away! And yes, having it in /var/www/ is a bad idea -- stuff like Django and Rails are "hooked into" the Web server in a different way than CGI scripts or PHP are, and do not need to be in the Apache document root. Having your source files there is thus a bad thing because they might be served up as plaintext -- allowing anyone to read your source code :(

bitprophet
Jul 22, 2004
Taco Defender

the talent deficit posted:

The multiple admin sites is exactly what I needed! I'd overlooked that innocuous little section at the bottom of the admin docs. Thanks!

Yea, it's A) a relatively new feature, B) not used all that often regardless (the OTHER stuff in newforms-admin is generally more interesting) and C) a pretty simple "feature" whose use is largely up to you. All of that combined leads to it getting that tiny little mention.

I kinda wish I'd been able to work an example of that into my book, but we had to release it sooner rather than later :(

Anyway, good luck using it -- let us know if you run into problems or find neat tricks to do with it. I personally haven't actually had to use >1 admin site yet :)

bitprophet
Jul 22, 2004
Taco Defender

ijustam posted:

Has anyone worked with matplotlib before? There are very few examples or documentation regarding Django integration :( I'm not really sure where to begin.

Or maybe someone can offer an alternative graphing library?

Well, the trick with Django is there is no "integration": Django is just plain old Python that happens to be modeling how the Web works. Using a given Python library is, in the base case, as simple as 'import matplotlib' wherever you're trying to use it.

What exactly are you trying to accomplish with matplotlib and Django? Serve up dynamically generated graph images?

Adbot
ADBOT LOVES YOU

bitprophet
Jul 22, 2004
Taco Defender

ijustam posted:

I guess what I'm struggling with is passing the output image as an HttpResponse. The one example I found does this:

code:
    response=django.http.HttpResponse(content_type='image/png')
    canvas.print_png(response)
    return response
:psyduck:

No need for psyduck -- that's actually how it's done! The deal is that HttpResponse is modeling exactly that: an HTTP response. HTTP responses are not always text -- every time vanilla Apache serves up a static image or whatnot, it's doing roughly the same thing that code does (stuffing binary image data into the HTTP response).

The main trick is that when you create your new Response, you specify its Content-Type HTTP header so the browser knows what to do with it. The other trick is that HttpResponse objects are "file-like" and can be written to by any Python code that expects to deal with a file object...like, say, a drawing library's canvas.print_png(file_obj) method ;) Again, this will work regardless of the actual data being written -- text or image or PDF or whatever.

the talent deficit posted:

Not ideal, but is there a reason you can't just save the image to the filesystem, then pass the url to the request context? (Or redirect to it)? Just setup a cron to periodically remove images.

This is another, probably better way, because -- as duck monster pointed out -- having Django dynamically generate the graph every request is going to have far worse performance than talent deficit's method, which lets you basically "cache" the result of your drawing/graphing code on the filesystem.

Therefore, depending on exactly what your situation is, you could only generate a new image once in a while and have that result statically served up outside of Django the rest of the time. For example, if you're graphing network traffic or some poo poo, it'd be fine to have the graph update itself every minute or every 5 minutes or whatever.

duck monster posted:

Set it up so that if it 404s , it will fall back to the code that generates the image, which would then serve the image up for it (and supress the 404 code from being sent to the browser, because thats bad mojo) and then in future django wouldnt even need to be hit for it.

This isn't really possible with Django, insofar as you have to load up Django at least a little bit before Django can know whether it's needed! But, as above, you can have Django skip the image creation code unless a new image needs to be generated, which will still be a big time saver. (Yes, you could write a new mod_python handler or modify Django's default one, but I'm guessing that's a bit over ijustam's head if he's still not 100% comfortable with HttpRequests :))

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply