I've got some code that I have to re-use for several views, so I would like to create a decorator so that I don't have to copy and paste many lines of code.
So the code I need to re-use in different views is:
@login_required
def add_custom_word_song(request, target_word, source_word, pk):
"""
Add new word
"""
if request.method == 'POST':
form = WordForm(request.POST)
if form.is_valid():
deck_name = form.cleaned_data['deck_name']
source_word = form.cleaned_data['source_word']
target_word = form.cleaned_data['target_word']
fluency = form.cleaned_data['fluency']
user = request.user
Word.objects.create(user=user, target_word=target_word,
source_word=source_word, deck_name=deck_name, fluency=fluency)
return HttpResponseRedirect(reverse('vocab:list'))
if request.method =="GET":
user = request.user
request.session['pk'] = pk
form = CustomWordForm(initial={'user': user, 'target_word': target_word, 'source_word': source_word, 'deck_name': 'My Words', 'fluency': 0})
return render(request, 'vocab/add_custom_initial_song.html', {'form': form, 'pk': pk})
And the only part of the code that will change for the other views is the template part in the last line. So I tried putting everything except the last line in the decorator, but then I get:
TypeError: add_custom_word() missing 3 required positional arguments: 'target_word', 'source_word', and 'pk'
I tried different variations of this, but still get the same error.
If I understood correct, you would like to write some code like:
def add_custom_word(request, target_word, second_word, pk):
...
@add_custom_word
def add_custom_word_song(request, target_word, second_word, pk):
return render(request, 'vocab/add_custom_initial_song.html', {'form': form, 'pk': pk})
In this case, if you called add_custom_word_song
it means:
add_custom_word_song = add_custom_word(add_custom_word_song)
add_custom_word_song()
Python while first pass add_custom_word_song
as the first argument to add_custom_word
when module initialing, they call the new add_custom_word_song
.
So you will get the error as you said: add_custom_word() missing 3 required positional arguments: 'target_word', 'source_word', and 'pk'
If you really want to use decorator, you need wrap it again:
def add_custom_word(func):
def wrapper(request, target_word, second_word, pk):
...
return func(request, target_word, second_word, pk)
return wrapper
@decorator
def add_custom_word_song(request, target_word, second_word, pk):
return render(request, 'vocab/add_custom_initial_song.html', {'form': form, 'pk': pk})
But if you only want to change the template file, consider use a registry to manage the template content!
Edit:
A simplest registry could like a dict:
registry = {
"song": "vocab/add_custom_initial_song.html",
"image": "vocab/add_custom_initial_image.html",
...
}
You could define some types as keys, then define the template files as values. So you can return it like:
def add_custom_word(...):
...
return render(request, registry[return_type], {'form': form, 'pk': pk})
If you have some complex conditions, you could have a custom registry class, and it would always have register
and match
methods.
class Registry(object):
def __init__(self):
self.registry_list = []
def register(self, conditions, template_file):
self.registry_list.append([conditions, template_file])
def match(self, condition):
for conditions, template_file in self.registry_list:
if match(condition, conditions):
return template_file
registry = Registry()
Then you could use this registry to get template files:
def add_custom_word(...):
...
return render(request, registry.match(condition), {'form': form, 'pk': pk})