[#8014] ticket:859 Bring OAuthConsumerToken.upsert() back (with a fix)
diff --git a/Allura/allura/model/oauth.py b/Allura/allura/model/oauth.py
index 6dc00e5..d467dc1 100644
--- a/Allura/allura/model/oauth.py
+++ b/Allura/allura/model/oauth.py
@@ -22,8 +22,9 @@
 
 from paste.deploy.converters import aslist
 from tg import config
+import pymongo
 from ming import schema as S
-from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty
+from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty, session
 from ming.orm.declarative import MappedClass
 
 from allura.lib import helpers as h
@@ -80,6 +81,20 @@
         return oauth.Consumer(self.api_key, self.secret_key)
 
     @classmethod
+    def upsert(cls, name, user):
+        params = dict(name=name, user_id=user._id)
+        t = cls.query.get(**params)
+        if t is not None:
+            return t
+        try:
+            t = cls(**params)
+            session(t).flush(t)
+        except pymongo.errors.DuplicateKeyError:
+            session(t).expunge(t)
+            t = cls.query.get(**params)
+        return t
+
+    @classmethod
     def for_user(cls, user=None):
         if user is None:
             user = c.user
diff --git a/Allura/allura/tests/model/test_oauth.py b/Allura/allura/tests/model/test_oauth.py
new file mode 100644
index 0000000..c565f5d
--- /dev/null
+++ b/Allura/allura/tests/model/test_oauth.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+
+from nose.tools import with_setup, assert_equal, assert_not_equal
+
+from ming.odm import ThreadLocalORMSession
+
+from allura import model as M
+from alluratest.controller import setup_basic_test, setup_global_objects
+
+
+def setUp():
+    setup_basic_test()
+    ThreadLocalORMSession.close_all()
+    setup_global_objects()
+
+
+@with_setup(setUp)
+def test_upsert():
+    admin = M.User.by_username('test-admin')
+    user = M.User.by_username('test-user')
+    name = 'test-token'
+    token1 = M.OAuthConsumerToken.upsert(name, admin)
+    token2 = M.OAuthConsumerToken.upsert(name, admin)
+    token3 = M.OAuthConsumerToken.upsert(name, user)
+    assert_equal(M.OAuthConsumerToken.query.find().count(), 2)
+    assert_equal(token1._id, token2._id)
+    assert_not_equal(token1._id, token3._id)