Django method same object not saving - python

This is something I have been trying to solve for 3 days now and I just can't get my head around it why this is not working.
I have a method that creates a new version of an Object. It used to work, that you would pass in the sou obj. and this would be the source from which a new version is created. You can also pass in a destination, which is not really important in this example. Now I wanted to add locking to this method as we want to add multiple users. So I want to be sure that I always have the most current object from which I create a new one. So I added a line that would just get the newest object. If there is no newer object in the database it would be the same anyway.
def createRevision(request, what, sou, destination=None, ignore = [], **args):
...
if "initial" not in args.keys():
source = get_object_or_404(BaseItem, ppk=sou.ppk, project=sou.project, current=True)
print "------------"
print source == sou
print "------------"
# This outputs True
else:
source = sou
further down in the method I do something like
source.current = False
source.save()
Basically the idea is that I pass in BaseItem and if I don't specify the "initial" keyword then I get the current item from that project with the same ppk (Which is a special random pk in conduction with current). I do this just to be on the save side, that I really have the most current object. And if it is the initial version I just use that one, as there can not be another version.
So now the problem is, that everything works fine if I use sou in this method. I can save it etc .. but as soon as I use source and initial is not in the args it just doesn't save it. The print statement tells me they are the same. Everything I print after the save tells me it has been saved but it just doesn't do it.
source.current = False
source.save()
print "SAVED !!!!"
print source.pk
print source.current
rofl = get_object_or_404(BaseItem, pk=source.pk, project=sou.project)
print rofl.pk
print source.current
outputs the same pk and the same current value but somehow it is not properly saved. As soon as I look into django admin or do a select current = True.
I really don't know what to do anymore.
Why does it work without a problem if I pass in the object into the method but starts to fail when I get the exact same object in the method?
Of course I call the method with the same object:
x = get_object_or_404(BaseItem, ppk=sou.ppk, project=sou.project, current=True)
createRevision(request, "", x)

Thank you pztrick for the hint with the caches. I finally solved it. So the problem was that I was doing:
x = get_object_or_404(BaseItem, ppk=sou.ppk, project=sou.project, current=True)
createRevision(request, "", x)
# .... loads of lines of code
unlock(x)
unlock is a method I wrote that just sets a timestamp so I know no other user is editing it. So now the problem was that I was saving x in createRevision with all the correct data but of course unlock(x) still had a reference to an "old" not updated object and of course was saving it again. Hence it was overwriting my changes in createRevision.
Thank you again to everyone who helped with this.

I think you may be running afoul of model manager caching which is intended to limit database queries. However, by invoking the .all() method on the model manager you force it to hit the databse again.
So, try this: Replace your argument from the BaseItem class to the model manager's .all() QuerySet:
source = get_object_or_404(BaseItem.objects.all(), ppk=sou.ppk, project=sou.project, current=True)
# ...
rofl = get_object_or_404(BaseItem.objects.all(), pk=source.pk, project=sou.project)
get_object_or_404 supports mode classes, model managers, or QuerySets as the first parameter so this is valid.

Related

How to reopen a closed Gitlab Projectissue with the python gitlab module

Trying to automate reopening a bunch of issues on a kanban board in Gitlab does not seem to be working as expected. I can edit pretty much everything else: labels, title, description, due date and so on, but changing the state from 'closed' to 'opened' is not doing anything.
Funny enough the save() method, takes some time to complete and is not throwing any error (but it does when I try to update an issue without having changed anything)
import gitlab
gl = gitlab.Gitlab(URL, private_token=TOKEN, api_version = 4)
gl.auth()
Project = gl.projects.get(project_id, lazy=True)
editable_issue = Project.issues.get(issue_id)
I use this to instantiate the gitlab object
This works:
editable_issue.labels.append('some label')
editable_issue.save()
editable_issue.title = title + '\n' + 'edited.'
editable_issue.save()
But this doesn't:
editable_issue.state = 'opened'
editable_issue.save()
I have also tried changing several fields, like so:
editable_issue.state = 'opened'
editable_issue.closed_at = None
editable_issue.save()
But, albeit I receive no error, the task is not being updated.
Is there something I'm overlooking ?
edit
Oh yes, I forgot to mention that the documentation seems to be mentioning state_event as a method, while my object does not know of such a method.
Is this method only there when a projectissue is being created by the api, perhaps ?
As mentioned in the documentation, you must set the state_event attribute, then save. This attribute won't exist at first -- you must create it.
issue.state_event = 'reopen'
issue.save()
Modifying state or closed_at won't work because the issues edit API doesn't accept state or closed_at parameters.
(this is also why state_event doesn't exist on the object at first, because the issue detail API doesn't include state_event in its response -- all the attributes are set dynamically by the response JSON)

append to request.sessions[list] in Django

Something is bugging me.
I'm following along with this beginner tutorial for django (cs50) and at some point we receive a string back from a form submission and want to add it to a list:
https://www.youtube.com/watch?v=w8q0C-C1js4&list=PLhQjrBD2T380xvFSUmToMMzERZ3qB5Ueu&t=5777s
def add(request):
if 'tasklist' not in request.session:
request.session['tasklist'] = []
if request.method == 'POST':
form_data = NewTaskForm(request.POST)
if form_data.is_valid():
task = form_data.cleaned_data['task']
request.session['tasklist'] += [task]
return HttpResponseRedirect(reverse('tasks:index'))
I've checked the type of request.session['tasklist']and python shows it's a list.
The task variable is a string.
So why doesn't request.session['tasklist'].append(task) work properly? I can see it being added to the list via some print statements but then it is 'forgotten again' - it doesn't seem to be permanently added to the tasklist.
Why do we use this request.session['tasklist'] += [task] instead?
The only thing I could find is https://ogirardot.wordpress.com/2010/09/17/append-objects-in-request-session-in-django/ but that refers to a site that no longer exists.
The code works fine, but I'm trying to understand why you need to use a different operation and can't / shouldn't use the append method.
Thanks.
The reason why it does not work is because django does not see that you have changed anything in the session by using the append() method on a list that is in the session.
What you are doing here is essentially pulling out the reference to the list and making changes to it without the session backend knowing anything about it. An other way to explain:
The append() method is on the list itself not on the session object
When you call append() on the list you are only talking to the list and the list's parent (the session) has no idea what you guys are doing
When you however do an assignment on the session itself session['whatever'] = 'something' then it knows that something is up and changes are made
So the key here is that you need to operate on the session object directly if you want your changes to be updated automatically
Django only thinks it needs to save a changed session item if the item got reassigned to the session. See here: django session base code the __setitem__ method containing a self.modified = True statement.
The session['list'] += [new_element] adds a new list item (mutates the list stored in the session, so the list reference stays the same) and then gets it reassigned to the session again -> thus triggering first a __getitem__ call -> then your += / __iadd__ runs on the value read -> then a __setitem__ call is made (with the list ref. passed to it). You can see it in the django codebase that it marks the session after each __setitem__ call as modified.
The session['list'] = session['list'] + [new_item] mode of doing the same does create a new list every time it's run so its a bit less efficient, but you should not store hundreds of items in the session anyway. So you're probably fine. This also works exactly as above.
However if you use sub-keys in the session like session['list']['x'] = 'whatever' the session will not see itself as modified so you need to mark it as by request.session.modified = True
Short answer: It's about how Python chooses to implement the dict data structure.
Long answer:
Let's start by saying that request.session is a dictionary.
Quoting Django's documentation, "By default, Django only saves to the session database when the session has been modified – that is if any of its dictionary values have been assigned or deleted". Link
So, the problem is that the session database is not being modified by
request.session['tasklist'].append(task)
Seeing the related parts Django's Session base code (as posted by #Csaba K. in an answer), the variable self.modified is to be set True when setitem dunder method is called.
Now, at this step the problem seems like the setitem dunder method is not being called with request.session['tasklist'].append(task) but with request.session['tasklist'] += [task] it gets called. It is not due to if the reference of request.session['tasklist'] is changing or not as pointed out by another answer, because the reference to the underlying list remains the same.
To confirm, let's create a custom dictionary which extends the Python dict, and print something when setitem dunder method is called.
class MyDict(dict):
def __init__(self, globalVar):
super().__init__()
self.globalVar = globalVar
def __setitem__(self, key, value):
super().__setitem__(key, value)
print("Called Set item when: ", end="")
myDict = MyDict(0)
print("Creating Dict")
print("-----")
myDict["y"] = []
print("Adding a new key-value pair")
print("-----")
myDict["y"] += ["x"]
print(" using +=")
print("-----")
myDict["y"].append("x")
print("append")
print("-----")
myDict["y"].extend(["x"])
print("extend")
print("-----")
myDict["y"] = myDict["y"] + ["x"]
print(" using +",)
print("-----")
It prints:
Creating Dict
-----
Called Set item when: Adding a new key-value pair
-----
Called Set item when: using +=
-----
append
-----
extend
-----
Called Set item when: using +
-----
As we can see, setitem dunder method is called and in turn self.modified is set true only when adding a new key-value pair, or using += or using +, but not when initializing, appending or extending an iterable (in this case a list). Now, the operator + and += do very different things in Python, as explained in the other answer. += behaves more like the append method but in this case, I guess it's more about how Python chooses to implement the dict data structure rather than how +, += and append behave on lists.
I found this while doing some more searching:
https://code.djangoproject.com/wiki/NewbieMistakes
Scroll to 'Appending to a list in session doesn't work'
Again, it is a very dated entry but still seems to hold true.
Not completely satisfied because this does not answer the question as to 'why' this doesn't work, but at the very least confirms 'something's up' and you should probably still use the recommendations there.
(if anyone out there can actually explain this in a more verbose manner then I'd be happy to hear it)

Using transactions in Django

I'm using django 1.11 on python 3.7
In a method I want to execute some database queries, mainly updating links between objects and I want to use this method to perform a check on what needs to be updated in a sync-operation. The following is an implementation:
results = {}
with transaction.atomic():
sid = transaction.savepoint()
for speaker_user in speaker_users:
# here my code checks all sorts of things, updates the database with
# new connections between objects and stores them all in the
# results-dict, using a lot of code in other classes which
# I really dont want to change for this operation
if sync_test_only:
transaction.savepoint_rollback(sid)
else:
transaction.savepoint_commit(sid)
return results
This snippet is used in a method with the sync_test_only parameter that should only fill the results-dict without doing the database changes that go along with it.
So this method can be used to do the actual work, when sync_test_only is False, and also only report back the work to-be-done, when sync_test_only is True
Is this what the transaction.atomic() is designed for? Does this actually work in my use-case? If not, what would be a better way to achieve this behaviour?
Another option would be to use exceptions, like the docs suggest (read the part under the title "You may need to manually revert model state when rolling back a transaction"):
class MyException(Exception):
pass
def f(do_commit=False):
results = {}
try:
with transaction.atomic():
for speaker_user in speaker_users:
pass
if not do_commit:
raise MyException
except MyException:
# do nothing here
pass
return results
I suggest creating a custom exception so you don't accidently catch something that was raised somewhere else in the code.

When does .save() create an object?

I have the code:
name = MakesiteNameForm(datdict)
if name.is_valid:
name.save()
datsite = Makesite.objects.get(sitename=request.POST['sitename'])
datsite.ref_id.add(RefID.objects.create(url=request.POST['url'],description=request.POST['description']))
datsite.save()
So i have this bit of code what I want to use to create and save some manytomany items but when I try using this method is says that Makesite matching query does not exist. which i think means it hasn't saved but then later I call site = Makesite.objects.all() and I can clearly see the value of what request.POST['sitename'] is sitting inside the querydict. So is there anyway to query this better? or is there something about the save() i missing?
Edit: that form saves a value sitename values into the Makesite table
The save() call doesn't create objects, it just saves the object to the database, inserting a new row in case it's a new object, or updating it.
First, form.is_valid() is a method, but you're not calling it, so you're always trying to save name. That may or may not be related to your error, but it's wrong anyway, and maybe that's where the query error is coming from, not the get() call below. Fix it and see what happens.

Django model object's construction fails silently

I have quite an obvious piece of code that fails:
temp = MyModel(
required_field1 = AnotherModel.objects.filter(name="example1")[0],
required_field2 = YetAnotherModel.objects.filter(name="example2")[0],
)
The problem is that after that, temp is set to None! I have no traceback, no error message - it just doesn't work and leaves None. required_fieldNs (for N=1|2) are the only mandatory fields in MyModel. Objects of AnotherModel and YetAnotherModel exist. Does anyone have any idea why it doesn't work as I'd like to (I mean it does not construct a new object, referenced by temp). I can't paste all my actual code in here, because it is a corporate project, but if in doubt - please ask and I can explain probably something more.
EDIT:
OK I figured out why it fails, the problem was that I was trying to invoke a method from that newly constructed object and it caused it to crash in this strange way. This topic can now be closed.
Unless required_field1 and required_field2 are foreign keys, the above code won't work.
Are you sure you didn't mean:
temp = MyModel(
required_field1 = unicode(AnotherModel.objects.filter(name="example1")[0]),
required_field2 = unicode(YetAnotherModel.objects.filter(name="example2")[0]),
)
Or:
temp = MyModel(
required_field1 = AnotherModel.objects.filter(name="example1")[0].some_field,
required_field2 = YetAnotherModel.objects.filter(name="example2")[0].some_field,
)

Categories

Resources