##// END OF EJS Templates
merge changelog with default (added 1.1.4 version)
merge changelog with default (added 1.1.4 version)

File last commit:

r1088:fee47261 beta
r1097:ecf25535 beta
Show More
db.py
341 lines | 17.8 KiB | text/x-python | PythonLexer
Models code cleanups
r759 # -*- coding: utf-8 -*-
"""
new improved models with helper functions for easier data fetching
r832 rhodecode.model.db
~~~~~~~~~~~~~~~~~~
Models code cleanups
r759
added current db version into rhodecode,...
r834 Database Models for RhodeCode
Models code cleanups
r759 :created_on: Apr 08, 2010
:author: marcink
fixed copyright year to 2011
r902 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
Models code cleanups
r759 :license: GPLv3, see COPYING for more details.
"""
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License or (at your opinion) any later version of the license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
import logging
import datetime
Added grouping by days in journal
r994 from datetime import date
Models code cleanups
r759
fixed imports
r658 from sqlalchemy import *
Models code cleanups
r759 from sqlalchemy.exc import DatabaseError
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 from sqlalchemy.orm import relationship, backref
fixed Session problems in model class functions...
r1081 from sqlalchemy.orm.interfaces import MapperExtension
Models code cleanups
r759
fixed Session problems in model class functions...
r1081 from rhodecode.model.meta import Base, Session
Models code cleanups
r759
renamed project to rhodecode
r547 log = logging.getLogger(__name__)
fixed Session problems in model class functions...
r1081 #==============================================================================
# MAPPER EXTENSIONS
#==============================================================================
class RepositoryMapper(MapperExtension):
def after_update(self, mapper, connection, instance):
pass
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class RhodeCodeSettings(Base):
renamed hg_app to rhodecode
r548 __tablename__ = 'rhodecode_settings'
renamed project to rhodecode
r547 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
fixed models for compatibility with database systems
r780 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
renamed project to rhodecode
r547
Fixed dbmigrate issues.
r907 def __init__(self, k='', v=''):
Added settings model, and Exceptions lib....
r704 self.app_settings_name = k
self.app_settings_value = v
def __repr__(self):
Extended repo2db mapper with group creation via directory structures...
r878 return "<%s('%s:%s')>" % (self.__class__.__name__,
self.app_settings_name, self.app_settings_value)
Added settings model, and Exceptions lib....
r704
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class RhodeCodeUi(Base):
renamed hg_app to rhodecode
r548 __tablename__ = 'rhodecode_ui'
renamed project to rhodecode
r547 __table_args__ = {'useexisting':True}
fixed models for compatibility with database systems
r780 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
Hacking for git support,and new faster repo scan
r631
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class User(Base):
renamed project to rhodecode
r547 __tablename__ = 'users'
__table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
fixed models for compatibility with database systems
r780 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
active = Column("active", Boolean(), nullable=True, unique=None, default=None)
admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
Thayne Harbaugh
Improve LDAP authentication...
r991 ldap_dn = Column("ldap_dn", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
Hacking for git support,and new faster repo scan
r631
#56 implemented users groups editing,...
r972 user_log = relationship('UserLog', cascade='all')
user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
Hacking for git support,and new faster repo scan
r631
#56 implemented users groups editing,...
r972 repositories = relationship('Repository')
user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
fixed #72 show warning on removal when user still is owner of existing repositories...
r713
extended admin rescan to show what repositories was added and what removed...
r1039 group_member = relationship('UsersGroupMember', cascade='all')
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065
Models code cleanups
r759 @property
renamed project to rhodecode
r547 def full_contact(self):
return '%s %s <%s>' % (self.name, self.lastname, self.email)
Hacking for git support,and new faster repo scan
r631
Added some more details into user edit permissions view
r895 @property
made simple global rss and atom feed
r1088 def short_contact(self):
return '%s %s' % (self.name, self.lastname)
@property
Added some more details into user edit permissions view
r895 def is_admin(self):
return self.admin
renamed project to rhodecode
r547 def __repr__(self):
Extended repo2db mapper with group creation via directory structures...
r878 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
self.user_id, self.username)
Hacking for git support,and new faster repo scan
r631
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 @classmethod
def by_username(cls, username):
return Session.query(cls).filter(cls.username == username).one()
renamed project to rhodecode
r547 def update_lastlogin(self):
"""Update user lastlogin"""
Hacking for git support,and new faster repo scan
r631
renamed project to rhodecode
r547 try:
session = Session.object_session(self)
self.last_login = datetime.datetime.now()
session.add(self)
session.commit()
log.debug('updated user %s lastlogin', self.username)
Models code cleanups
r759 except (DatabaseError,):
Hacking for git support,and new faster repo scan
r631 session.rollback()
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class UserLog(Base):
renamed project to rhodecode
r547 __tablename__ = 'user_logs'
__table_args__ = {'useexisting':True}
fixed models for compatibility with database systems
r780 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
removed unicode from models string params
r880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
fixed models for compatibility with database systems
r780 repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
Hacking for git support,and new faster repo scan
r631
Added grouping by days in journal
r994 @property
def action_as_day(self):
return date(*self.action_date.timetuple()[:3])
#56 implemented users groups editing,...
r972 user = relationship('User')
repository = relationship('Repository')
Hacking for git support,and new faster repo scan
r631
started working on issue #56
r956
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class UsersGroup(Base):
started working on issue #56
r956 __tablename__ = 'users_groups'
__table_args__ = {'useexisting':True}
#56 fixed found bugs, implemented adding of new group + forms+validators...
r959 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
users_group_name = Column("users_group_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
started working on issue #56
r956
#56 fixed relationship query behavior to speed up fetching, and...
r974 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
started working on issue #56
r956
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class UsersGroupMember(Base):
started working on issue #56
r956 __tablename__ = 'users_groups_members'
__table_args__ = {'useexisting':True}
#56 fixed found bugs, implemented adding of new group + forms+validators...
r959 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
started working on issue #56
r956 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
#56 fixed relationship query behavior to speed up fetching, and...
r974 user = relationship('User', lazy='joined')
#56 implemented users groups editing,...
r972 users_group = relationship('UsersGroup')
updated db migrations to schema 3
r1023 def __init__(self, gr_id='', u_id=''):
#56 implemented users groups editing,...
r972 self.users_group_id = gr_id
self.user_id = u_id
started working on issue #56
r956
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class Repository(Base):
renamed project to rhodecode
r547 __tablename__ = 'repositories'
__table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
fixed Session problems in model class functions...
r1081 __mapper_args__ = {'extension':RepositoryMapper()}
fixed models for compatibility with database systems
r780 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
dbmigrations:...
r836 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
removed unicode from models string params
r880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
fixed models for compatibility with database systems
r780 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
fixes #62, added option to disable statistics for each repository
r810 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
implemented #84 downloads can be enabled/disabled per each repository from now.
r962 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
fixed models for compatibility with database systems
r780 description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
removed unicode from models string params
r880 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
Hacking for git support,and new faster repo scan
r631
#56 implemented users groups editing,...
r972 user = relationship('User')
fork = relationship('Repository', remote_side=repo_id)
group = relationship('Group')
fixed problem with caching
r1033 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
#56 added ajax removal of users groups,...
r1015 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
#56 implemented users groups editing,...
r972 stats = relationship('Statistics', cascade='all', uselist=False)
fixes #51 deleting a repo didn't delete it's dependent db entries....
r667
updated config files, and changed model repo_followers to followers
r1034 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
implemented user dashboards, and following system.
r734
Fixed links for repository, it's optional now to pass a link without a summary...
r976 logs = relationship('UserLog', cascade='all')
fixees for #106 relation issues on databases different than sqlite
r970
renamed project to rhodecode
r547 def __repr__(self):
Extended repo2db mapper with group creation via directory structures...
r878 return "<%s('%s:%s')>" % (self.__class__.__name__,
removed unicode from models string params
r880 self.repo_id, self.repo_name)
Extended repo2db mapper with group creation via directory structures...
r878
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 @classmethod
def by_repo_name(cls, repo_name):
return Session.query(cls).filter(cls.repo_name == repo_name).one()
class Group(Base):
Extended repo2db mapper with group creation via directory structures...
r878 __tablename__ = 'groups'
__table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
group_name = Column("group_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
removed unicode from models string params
r880 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
Extended repo2db mapper with group creation via directory structures...
r878
#56 implemented users groups editing,...
r972 parent_group = relationship('Group', remote_side=group_id)
Extended repo2db mapper with group creation via directory structures...
r878
def __init__(self, group_name='', parent_group=None):
self.group_name = group_name
self.parent_group = parent_group
def __repr__(self):
return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
self.group_name)
Hacking for git support,and new faster repo scan
r631
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class Permission(Base):
renamed project to rhodecode
r547 __tablename__ = 'permissions'
__table_args__ = {'useexisting':True}
fixed models for compatibility with database systems
r780 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
Hacking for git support,and new faster repo scan
r631
renamed project to rhodecode
r547 def __repr__(self):
Extended repo2db mapper with group creation via directory structures...
r878 return "<%s('%s:%s')>" % (self.__class__.__name__,
removed unicode from models string params
r880 self.permission_id, self.permission_name)
renamed project to rhodecode
r547
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class RepoToPerm(Base):
renamed project to rhodecode
r547 __tablename__ = 'repo_to_perm'
__table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
fixed models for compatibility with database systems
r780 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
removed unicode from models string params
r880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
Hacking for git support,and new faster repo scan
r631
#56 implemented users groups editing,...
r972 user = relationship('User')
permission = relationship('Permission')
repository = relationship('Repository')
renamed project to rhodecode
r547
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class UserToPerm(Base):
renamed project to rhodecode
r547 __tablename__ = 'user_to_perm'
__table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
fixed models for compatibility with database systems
r780 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
removed unicode from models string params
r880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
Hacking for git support,and new faster repo scan
r631
#56 implemented users groups editing,...
r972 user = relationship('User')
permission = relationship('Permission')
renamed project to rhodecode
r547
#56 added assignments of users groups into repository
r1014
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class UsersGroupToPerm(Base):
added user group to perm table
r958 __tablename__ = 'users_group_to_perm'
__table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
#56 fixed found bugs, implemented adding of new group + forms+validators...
r959 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
added user group to perm table
r958 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
#56 added assignments of users groups into repository
r1014 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
added user group to perm table
r958
#56 implemented users groups editing,...
r972 users_group = relationship('UsersGroup')
permission = relationship('Permission')
#56 added assignments of users groups into repository
r1014 repository = relationship('Repository')
added user group to perm table
r958
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class GroupToPerm(Base):
added group to perm mapping table
r879 __tablename__ = 'group_to_perm'
__table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
removed unicode from models string params
r880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
added group to perm mapping table
r879
#56 implemented users groups editing,...
r972 user = relationship('User')
permission = relationship('Permission')
group = relationship('Group')
added group to perm mapping table
r879
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class Statistics(Base):
renamed project to rhodecode
r547 __tablename__ = 'statistics'
__table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
fixed models for compatibility with database systems
r780 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
removed unicode from models string params
r880 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
fixed models for compatibility with database systems
r780 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data
commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
languages = Column("languages", LargeBinary(), nullable=False)#JSON data
Hacking for git support,and new faster repo scan
r631
#56 implemented users groups editing,...
r972 repository = relationship('Repository', single_parent=True)
renamed project to rhodecode
r547
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class UserFollowing(Base):
implemented user dashboards, and following system.
r734 __tablename__ = 'user_followings'
__table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
UniqueConstraint('user_id', 'follows_user_id')
, {'useexisting':True})
fixed models for compatibility with database systems
r780 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
removed unicode from models string params
r880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
implemented user dashboards, and following system.
r734
#56 implemented users groups editing,...
r972 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
implemented user dashboards, and following system.
r734
#56 implemented users groups editing,...
r972 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
Optimized queries on journal, and added quick stop following action button in journal
r1000 follows_repository = relationship('Repository', order_by='Repository.repo_name')
implemented user dashboards, and following system.
r734
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class CacheInvalidation(Base):
Adde table for cache invalidation
r670 __tablename__ = 'cache_invalidation'
#50 on point cache invalidation changes....
r692 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
fixed models for compatibility with database systems
r780 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
Adde table for cache invalidation
r670
#50 on point cache invalidation changes....
r692 def __init__(self, cache_key, cache_args=''):
self.cache_key = cache_key
self.cache_args = cache_args
self.cache_active = False
def __repr__(self):
Extended repo2db mapper with group creation via directory structures...
r878 return "<%s('%s:%s')>" % (self.__class__.__name__,
self.cache_id, self.cache_key)
added current db version into rhodecode,...
r834
Moved BaseModel into base class for declarative base. Added some handy methods into...
r1065 class DbMigrateVersion(Base):
added current db version into rhodecode,...
r834 __tablename__ = 'db_migrate_version'
__table_args__ = {'useexisting':True}
repository_id = Column('repository_id', String(250), primary_key=True)
repository_path = Column('repository_path', Text)
version = Column('version', Integer)